Bloxd Code Content Assist

Bloxd Code Content Assist! Have 13 objects and 20 Callbacks!

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         Bloxd Code Content Assist
// @namespace    http://tampermonkey.net/
// @version      1.2.0
// @description  Bloxd Code Content Assist! Have 13 objects and 20 Callbacks!
// @author       Vicaloser
// @match        https://bloxd.io/*
// @grant        none
// ==/UserScript==
(function() {
    'use strict';
    let menu = null;
    let selectedIndex = 0;
    let currentFilteredList = [];
    let activeTextArea = null;
    //環境狀態偵測
    function getEditorMode() {
        const wrapper = document.querySelector('.WriteOnBoardWrapper');
        if (!wrapper) return "BLOCK";
        const style = window.getComputedStyle(wrapper, '::after').getPropertyValue('content');
        return (style && style.includes("World Code")) ? "WORLD" : "BLOCK";
    }
    function getFunctionNameAtCursor(text, pos) {
        const textBefore = text.substring(0, pos);
        const lastOpenBracket = textBefore.lastIndexOf('(');
        if (lastOpenBracket === -1) return null;
        const textAfterBracket = textBefore.substring(lastOpenBracket);
        if (textAfterBracket.includes(')')) return null;
        const funcMatch = textBefore.substring(0, lastOpenBracket).match(/[\w.]+$/);
        return funcMatch ? funcMatch[0] : null;
    }
    //字典
    function getDict() {
        const mode = getEditorMode();
        const idVar = (mode === "WORLD") ? "playerId" : "myId";
        //code推薦
        let base = [
            { label: "const", replace: "const $0 = " },
            { label: "let", replace: "let $0 = " },
            { label: "var", replace: "var $0 = " },
            { label: "function", replace: "function $0() {\n    \n}" },
            { label: "if", replace: "if ($0) {\n    \n}" },
            { label: "else", replace: "else {\n    $0\n}" },
            { label: "console.log", replace: "console.log($0)" },
            { label: "api.sendMessage", replace: `api.sendMessage(${idVar}, "$0")` },
            { label: "api.getPosition", replace: `api.getPosition(${idVar})` },
            { label: "eval()", replace: 'eval($0)' },
            { label: "return", replace: 'return' },
            { label: "true", replace: 'true' },
            { label: "false", replace: 'false' },
            { label: "api.setPosition", replace: `api.setPosition(${idVar}, x, y, z)` },
        ];
        //world code區
        if (mode === "WORLD") {
            base.push(
                /*事件*/
                { label: "onPlayerJoin", replace: "onPlayerJoin = (playerId, fromGameReset) => {\n    $0\n}" },
                { label: "onPlayerLeave", replace: "onPlayerLeave = (playerId, serverIsShuttingDown) => {\n    $0\n}" },
                { label: "onPlayerChat", replace: "onPlayerChat = (playerId, message) => {\n    $0\n}" },
                { label: "tick", replace: "tick = () => {\n    $0\n}" },
                { label: "onClose", replace: "onClose = (serverIsShuttingDown) => {\n    $0\n}" },
                { label: "onPlayerJump", replace: "onPlayerJump = (playerId) => {\n    $0\n}" },
                { label: "onRespawnRequest", replace: "onRespawnRequest = (playerId) => {\n    $0\n}" },
                { label: "playerCommand", replace: "playerCommand = (playerId, command) => {\n    $0\n}" },
                { label: "onPlayerChangeBlock", replace: "onPlayerChangeBlock = (playerId, x, y, z, fromBlock, toBlock, droppedItem) => {\n    $0\n}" },
                { label: "onPlayerDropItem", replace: "onPlayerDropItem = (playerId, x, y, z, itemName, itemAmount, fromIdx) => {\n    $0\n}" },
                { label: "onPlayerPickedUpItem", replace: "onPlayerPickedUpItem = (playerId, itemName, itemAmount) => {\n    $0\n}" },
                { label: "onPlayerSelectInventorySlot", replace: "onPlayerSelectInventorySlot = (playerId, slotIndex) => {\n    $0\n}" },
                { label: "onBlockStand", replace: "onBlockStand = (playerId, x, y, z, blockName) => {\n    $0\n}" },
                { label: "onPlayerAttemptCraft", replace: "onPlayerAttemptCraft = (playerId, itemName, craftingIdx, craftTimes) => {\n    $0\n}" },
                { label: "onPlayerCraft", replace: "onPlayerCraft = (playerId, itemName, craftingIdx, recipe, craftTimes) => {\n    $0\n}" },
                { label: "onPlayerAttemptOpenChest", replace: "onPlayerAttemptOpenChest = (playerId, x, y, z, isMoonstoneChest, isIronChest) => {\n    $0\n}" },
                { label: "onPlayerOpenedChest", replace: "onPlayerOpenedChest = (playerId, x, y, z, isMoonstoneChest, isIronChest) => {\n    $0\n}" },
                { label: "onPlayerMoveItemOutOfInventory", replace: "onPlayerMoveItemOutOfInventory = (playerId, itemName, itemAmount, fromIdx, movementType) => {\n    $0\n}" },
                { label: "onPlayerMoveInvenItem", replace: "onPlayerMoveInvenItem = (playerId, fromIdx, toStartIdx, toEndIdx, amt) => {\n    $0\n}" },
                { label: "onPlayerMoveItemIntoIdxs", replace: "onPlayerMoveItemIntoIdxs = (playerId, start, end, moveIdx, itemAmount) => {\n    $0\n}" },
                { label: "onPlayerSwapInvenSlots", replace: "onPlayerSwapInvenSlots = (playerId, i, j) => {\n    $0\n}" },
                { label: "onPlayerMoveInvenItemWithAmt", replace: "onPlayerMoveInvenItemWithAmt = (playerId, i, j, amt) => {\n    $0\n}" },
                { label: "onPlayerAttemptAltAction", replace: "onPlayerAttemptAltAction = (playerId, x, y, z, block, targetEId) => {\n    $0\n}" },
                { label: "onPlayerAltAction", replace: "onPlayerAltAction = (playerId, x, y, z, block, targetEId) => {\n    $0\n}" },
                { label: "onPlayerClick", replace: "onPlayerClick = (playerId, wasAltClick, x, y, z, block, targetEId) => {\n    $0\n}" },
                { label: "onClientOptionUpdated", replace: "onClientOptionUpdated = (playerId, option, value) => {\n    $0\n}" },
                { label: "onMobSettingUpdated", replace: "onMobSettingUpdated = (mobId, setting, value) => {\n    $0\n}" },
                { label: "onInventoryUpdated", replace: "onInventoryUpdated = (playerId) => {\n    $0\n}" },
                { label: "onChestUpdated", replace: "onChestUpdated = (initiatorEId, isMoonstoneChest, x, y, z) => {\n    $0\n}" },
                { label: "onWorldChangeBlock", replace: "onWorldChangeBlock = (x, y, z, fromBlock, toBlock, initiatorDbId, extraInfo) => {\n    $0\n}" },
                { label: "onCreateBloxdMeshEntity", replace: "onCreateBloxdMeshEntity = (eId, type, initiatorId) => {\n    $0\n}" },
                { label: "onPlayerAttemptSpawnMob", replace: "onPlayerAttemptSpawnMob = (playerId, mobType, x, y, z) => {\n    $0\n}" },
                { label: "onEntityCollision", replace: "onEntityCollision = (eId, otherEId) => {\n    $0\n}" },
                { label: "onWorldAttemptSpawnMob", replace: "onWorldAttemptSpawnMob = (mobType, x, y, z) => {\n    $0\n}" },
                { label: "onPlayerSpawnMob", replace: "onPlayerSpawnMob = (playerId, mobId, mobType, x, y, z, mobHerdId, playSoundOnSpawn) => {\n    $0\n}" },
                { label: "onWorldSpawnMob", replace: "onWorldSpawnMob = (mobId, mobType, x, y, z, mobHerdId, playSoundOnSpawn) => {\n    $0\n}" },
                { label: "onWorldAttemptDespawnMob", replace: "onWorldAttemptDespawnMob = (mobId) => {\n    $0\n}" },
                { label: "onMobDespawned", replace: "onMobDespawned = (mobId) => {\n    $0\n}" },
                { label: "onPlayerAttack", replace: "onPlayerAttack = (playerId) => {\n    $0\n}" },
                { label: "onPlayerDamagingOtherPlayer", replace: "onPlayerDamagingOtherPlayer = (attackingPlayer, damagedPlayer, damageDealt, withItem, bodyPartHit, damagerDbId) => {\n    $0\n}" },
                { label: "onPlayerDamagingMob", replace: "onPlayerDamagingMob = (playerId, mobId, damageDealt, withItem, damagerDbId) => {\n    $0\n}" },
                { label: "onMobDamagingPlayer", replace: "onMobDamagingPlayer = (attackingMob, damagedPlayer, damageDealt, withItem) => {\n    $0\n}" },
                { label: "onMobDamagingOtherMob", replace: "onMobDamagingOtherMob = (attackingMob, damagedMob, damageDealt, withItem) => {\n    $0\n}" },
                { label: "onAttemptKillPlayer", replace: "onAttemptKillPlayer = (killedPlayer, attackingLifeform) => {\n    $0\n}" },
                { label: "onPlayerKilledOtherPlayer", replace: "onPlayerKilledOtherPlayer = (attackingPlayer, killedPlayer, damageDealt, withItem) => {\n    $0\n}" },
                { label: "onMobKilledPlayer", replace: "onMobKilledPlayer = (attackingMob, killedPlayer, damageDealt, withItem) => {\n    $0\n}" },
                { label: "onPlayerKilledMob", replace: "onPlayerKilledMob = (playerId, mobId, damageDealt, withItem) => {\n    $0\n}" },
                { label: "onMobKilledOtherMob", replace: "onMobKilledOtherMob = (attackingMob, killedMob, damageDealt, withItem) => {\n    $0\n}" },
                { label: "onPlayerPotionEffect", replace: "onPlayerPotionEffect = (initiatorId, targetId, effectName) => {\n    $0\n}" },
                { label: "onPlayerDamagingMeshEntity", replace: "onPlayerDamagingMeshEntity = (playerId, damagedId, damageDealt, withItem) => {\n    $0\n}" },
                { label: "onPlayerBreakMeshEntity", replace: "onPlayerBreakMeshEntity = (playerId, entityId) => {\n    $0\n}" },
                { label: "onPlayerUsedThrowable", replace: "onPlayerUsedThrowable = (playerId, throwableName, thrownEntityId) => {\n    $0\n}" },
                { label: "onPlayerThrowableHitTerrain", replace: "onPlayerThrowableHitTerrain = (playerId, throwableName, thrownEntityId) => {\n    $0\n}" },
                { label: "onTouchscreenActionButton", replace: "onTouchscreenActionButton = (playerId, touchDown) => {\n    $0\n}" },
                { label: "onTaskClaimed", replace: "onTaskClaimed = (playerId, taskId, isPromoTask, claimedRewards) => {\n    $0\n}" },
                { label: "onChunkLoaded", replace: "onChunkLoaded = (chunkId, chunk, wasPersistedChunk) => {\n    $0\n}" },
                { label: "onPlayerRequestChunk", replace: "onPlayerRequestChunk = (playerId, chunkX, chunkY, chunkZ, chunkId) => {\n    $0\n}" },
                { label: "onItemDropCreated", replace: "onItemDropCreated = (itemEId, itemName, itemAmount, x, y, z) => {\n    $0\n}" },
                { label: "onPlayerStartChargingItem", replace: "onPlayerStartChargingItem = (playerId, itemName) => {\n    $0\n}" },
                { label: "onPlayerFinishChargingItem", replace: "onPlayerFinishChargingItem = (playerId, used, itemName, duration) => {\n    $0\n}" },
                { label: "onPlayerFinishQTE", replace: "onPlayerFinishQTE = (playerId, qteId, result) => {\n    $0\n}" },
                { label: "onPlayerBoughtShopItem", replace: "onPlayerBoughtShopItem = (playerId, categoryKey, itemKey, item, userInput) => {\n    $0\n}" },
                { label: "doPeriodicSave", replace: "doPeriodicSave = () => {\n    $0\n}" }
            );
        }

        return base;
    }
    //邏輯處理
    function confirmSelection() {
        const item = currentFilteredList[selectedIndex];
        if (!item || !activeTextArea) return;

        const el = activeTextArea;
        const pos = el.selectionStart;
        const fullText = el.value;

        const textBeforeCursor = fullText.substring(0, pos);
        const match = textBeforeCursor.match(/[\w.{(]+$/);

        if (match) {
            const wordToReplace = match[0];
            const startPos = pos - wordToReplace.length;

            let replacement = item.replace || item.label;
            const hasPlaceholder = replacement.includes("$0");
            const cursorOffset = hasPlaceholder ? replacement.indexOf("$0") : replacement.length;
            replacement = replacement.replace("$0", "");

            const newText = fullText.substring(0, startPos) + replacement + fullText.substring(pos);

            el.focus();
            el.value = newText;

            const finalPos = startPos + cursorOffset;
            el.setSelectionRange(finalPos, finalPos);

            const eventProps = { bubbles: true, cancelable: true };
            el.dispatchEvent(new InputEvent('input', eventProps));
            el.dispatchEvent(new Event('change', eventProps));

            const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
            nativeInputValueSetter.call(el, newText);
            el.dispatchEvent(new Event('input', { bubbles: true }));
        }

        hideMenu();
        setTimeout(() => el.focus(), 10);
    }
    //UI渲染與監聽

    document.addEventListener('input', (e) => {
        if (!e.target.classList.contains('WriteOnBoardTextArea')) return;
        activeTextArea = e.target;
        const pos = e.target.selectionStart;
        const text = e.target.value;

        const match = text.substring(0, pos).match(/[\w.{]+$/);

        if (match) {
            const currentInput = match[0].toLowerCase();
            let suggestions = [...getDict()];

            if (getFunctionNameAtCursor(text, pos) === "api.sendMessage") {
                suggestions.push({ label: "{color}", replace: '{color: "$0"}' });
            }

            const words = text.match(/[a-zA-Z_]\w+/g) || [];
            const context = [...new Set(words)]
                .filter(w => w.toLowerCase() !== currentInput && w.length > 2)
                .map(w => ({ label: w }));

            currentFilteredList = [...suggestions, ...context].filter(it =>
                it.label.toLowerCase().includes(currentInput) && it.label.toLowerCase() !== currentInput
            );

            if (currentFilteredList.length > 0) {
                const coords = getCaretXY(e.target, pos);
                createMenu();
                renderMenu(currentFilteredList, coords.x, coords.y + 22);
            } else hideMenu();
        } else hideMenu();
    });

    function createMenu() {
        if (menu) return menu;
        menu = document.createElement('div');
        menu.style.cssText = `position: fixed; background: rgba(30,30,33,0.98); color: #d4d4d4; border: 1px solid #454545; box-shadow: 0 10px 25px rgba(0,0,0,0.5); z-index: 100000; display: none; font-family: 'JetBrains Mono', monospace; font-size: 13px; border-radius: 5px; overflow: hidden; min-width: 240px;`;
        document.body.appendChild(menu);
        return menu;
    }

    function renderMenu(list, x, y) {
        menu.innerHTML = '';
        list.forEach((item, i) => {
            const row = document.createElement('div');
            row.style.cssText = `padding: 6px 12px; display: flex; align-items: center; cursor: pointer; background: ${i === selectedIndex ? '#094771' : 'transparent'};`;
            row.innerHTML = `<span style="margin-right:8px; font-size:10px;">${item.replace ? '➤' : '✦'}</span><span style="flex-grow:1">${item.label}</span>`;
            row.onmousedown = (e) => { e.preventDefault(); selectedIndex = i; confirmSelection(); };
            menu.appendChild(row);
        });
        menu.style.left = x + 'px'; menu.style.top = y + 'px'; menu.style.display = 'block';
    }

    function getCaretXY(el, pos) {
        const div = document.createElement('div');
        const style = window.getComputedStyle(el);
        div.style.cssText = style.cssText;
        div.style.position = 'absolute'; div.style.visibility = 'hidden'; div.style.whiteSpace = 'pre-wrap';
        div.style.width = el.offsetWidth + 'px';
        div.textContent = el.value.substring(0, pos);
        const span = document.createElement('span');
        span.textContent = el.value.substring(pos, pos+1) || '.';
        div.appendChild(span);
        document.body.appendChild(div);
        const rect = el.getBoundingClientRect();
        const xy = { x: rect.left + span.offsetLeft - el.scrollLeft, y: rect.top + span.offsetTop - el.scrollTop };
        document.body.removeChild(div);
        return xy;
    }

    function hideMenu() { if(menu) menu.style.display = 'none'; selectedIndex = 0; }

    document.addEventListener('keydown', (e) => {
        if (!menu || menu.style.display === 'none') return;
        if (e.key === 'Tab' || e.key === 'Enter') { e.preventDefault(); confirmSelection(); }
        else if (e.key === 'ArrowDown') { e.preventDefault(); selectedIndex = (selectedIndex + 1) % currentFilteredList.length; renderMenu(currentFilteredList, parseInt(menu.style.left), parseInt(menu.style.top)); }
        else if (e.key === 'ArrowUp') { e.preventDefault(); selectedIndex = (selectedIndex - 1 + currentFilteredList.length) % currentFilteredList.length; renderMenu(currentFilteredList, parseInt(menu.style.left), parseInt(menu.style.top)); }
        else if (e.key === 'Escape') hideMenu();
    }, true);

    document.addEventListener('mousedown', (e) => { if(menu && !menu.contains(e.target)) hideMenu(); });
})();