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(); });
})();