Bloxd Code Content Assist

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

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

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

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

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