您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
recipe tracking + other various tweaks for infinite craft
当前为
// ==UserScript== // @name infinite craft tweaks // @namespace https://github.com/adrianmgg // @version 2.2.0 // @description recipe tracking + other various tweaks for infinite craft // @author amgg // @match https://neal.fun/infinite-craft/ // @icon https://neal.fun/favicons/infinite-craft.png // @grant unsafeWindow // @grant GM_setValue // @grant GM_getValue // @run-at document-idle // @compatible chrome // @compatible firefox // @license MIT // ==/UserScript== (function() { 'use strict'; const elhelper = (function() { /* via https://github.com/adrianmgg/elhelper */ function setup(elem, { style: { vars: styleVars = {}, ...style } = {}, attrs = {}, dataset = {}, events = {}, classList = [], children = [], parent = null, insertBefore = null, ...props }) { for (const k in style) elem.style[k] = style[k]; for (const k in styleVars) elem.style.setProperty(k, styleVars[k]); for (const k in attrs) elem.setAttribute(k, attrs[k]); for (const k in dataset) elem.dataset[k] = dataset[k]; for (const k in events) elem.addEventListener(k, events[k]); for (const c of classList) elem.classList.add(c); for (const k in props) elem[k] = props[k]; for (const c of children) elem.appendChild(c); if (parent !== null) { if (insertBefore !== null) parent.insertBefore(elem, insertBefore); else parent.appendChild(elem); } return elem; } function create(tagName, options = {}) { return setup(document.createElement(tagName), options); } function createNS(namespace, tagName, options = {}) { return setup(document.createElementNS(namespace, tagName), options); } return {setup, create, createNS}; })(); const GM_VALUE_KEY = 'infinitecraft_observed_combos'; // TODO this should probably use the async versions of getvalue/setvalue since we're already only calling it from async code function saveCombo(lhs, rhs, result) { console.log(`crafted ${lhs} + ${rhs} -> ${result}`); const data = GM_getValue(GM_VALUE_KEY, {}); if(!(result in data)) data[result] = []; for(const [a, b] in data[result]) { if(a === lhs && b === rhs) return; } data[result].push([lhs, rhs]); GM_setValue(GM_VALUE_KEY, data); } function getCombos() { return GM_getValue(GM_VALUE_KEY, {}); } function* iterCombos() { const data = getCombos; for(const result in data) { for(const [lhs, rhs] of data[result]) { yield {lhs, rhs, result}; } } } function main() { const _getCraftResponse = icMain.getCraftResponse; icMain.getCraftResponse = async function(lhs, rhs) { const resp = await _getCraftResponse.apply(this, arguments); saveCombo(lhs.text, rhs.text, resp.result); return resp; }; // random element thing document.documentElement.addEventListener('mousedown', e => { if(e.buttons === 1 && e.altKey) { // left mouse + alt e.preventDefault(); e.stopPropagation(); const elements = icMain._data.elements; const randomElement = elements[Math.floor(Math.random() * elements.length)]; icMain.selectElement(e, randomElement); } }, {capture: true}); // get the dataset thing they use for scoping css stuff // TODO add some better handling for if there's zero/multiple dataset attrs on that element in future const cssScopeDatasetThing = Object.keys(icMain.$el.dataset)[0]; function mkElementItem(element) { return elhelper.create('div', { classList: ['item'], dataset: {[cssScopeDatasetThing]: ''}, children: [ elhelper.create('span', { classList: ['item-emoji'], dataset: {[cssScopeDatasetThing]: ''}, textContent: element.emoji, style: { pointerEvents: 'none', }, }), document.createTextNode(` ${element.text} `), ], }); } // recipes popup const recipesListContainer = elhelper.create('div', { }); function updateRecipesList() { while(recipesListContainer.firstChild !== null) recipesListContainer.removeChild(recipesListContainer.firstChild); // build a name -> element map const byName = {}; for(const element of icMain._data.elements) byName[element.text] = element; function getByName(name) { return byName[name] ?? {emoji: "❌", text: `[userscript encountered an error trying to look up element '${name}']`}; } const combos = getCombos(); function listItemClick(evt) { const elementName = evt.target.dataset.comboviewerElement; document.querySelector(`[data-comboviewer-section="${CSS.escape(elementName)}"]`).scrollIntoView({block: 'nearest'}); } function mkLinkedElementItem(element) { return elhelper.setup(mkElementItem(element), { events: { click: listItemClick }, dataset: { comboviewerElement: element.text }, }); } for(const comboResult in combos) { if(comboResult === 'Nothing') continue; // anchor for jumping to recipesListContainer.appendChild(elhelper.create('div', { dataset: { comboviewerSection: comboResult }, })); for(const [lhs, rhs] of combos[comboResult]) { recipesListContainer.appendChild(elhelper.create('div', { children: [ mkLinkedElementItem(getByName(comboResult)), document.createTextNode(' = '), mkLinkedElementItem(getByName(lhs)), document.createTextNode(' + '), mkLinkedElementItem(getByName(rhs)), ], })); } } } const recipesDialog = elhelper.create('dialog', { parent: document.body, children: [ // close button elhelper.create('button', { textContent: 'x', events: { click: (evt) => recipesDialog.close(), }, }), // the main content recipesListContainer, ], style: { // need to unset this one thing from the page css margin: 'auto', }, }); // recipes button function addControlsButton(label, handler) { elhelper.create('div', { parent: document.querySelector('.side-controls'), textContent: label, style: { cursor: 'pointer', }, events: { click: handler, }, }); } addControlsButton('recipes', () => { updateRecipesList(); recipesDialog.showModal(); }); // first discoveries list (just gonna hijack the recipes popup for simplicity) addControlsButton('discoveries', () => { while(recipesListContainer.firstChild !== null) recipesListContainer.removeChild(recipesListContainer.firstChild); elhelper.setup(recipesListContainer, { children: icMain._data.elements.filter(e => e.discovered).map(mkElementItem), }); recipesDialog.showModal(); }); } // stores the object where most of the infinite craft functions live. // can be assumed to be set by the time main is called let icMain = null; // need to wait for stuff to be actually initialized. // might be an actual thing we can hook into to detect that // but for now just waiting until the function we want exists works well enough (function waitForReady(){ icMain = unsafeWindow?.$nuxt?._route?.matched?.[0]?.instances?.default; if(icMain !== undefined && icMain !== null) main(); else setTimeout(waitForReady, 10); })(); })();