Greasy Fork is available in English.
Bloxd Code Content Assist! Have 13 objects and 20 Callbacks!
// ==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(); });
})();