您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
QoL improvements for CYOAs made in IntCyoaCreator
当前为
// ==UserScript== // @name IntCyoaEnhancer // @namespace https://agregen.gitlab.io/ // @version 0.2.4 // @description QoL improvements for CYOAs made in IntCyoaCreator // @author agreg // @license MIT // @match https://*.neocities.org/* // @icon https://intcyoacreator.onrender.com/favicon.ico? // @run-at document-start // @grant unsafeWindow // @grant GM_registerMenuCommand // ==/UserScript== (function() { 'use strict'; // overriding AJAX sender (before the page starts loading) to detect project.json download done at init time let init, enhance, _XHR = unsafeWindow.XMLHttpRequest; unsafeWindow.XMLHttpRequest = class XHR extends _XHR { constructor () { super(); let _open = this.open; this.open = (...args) => { if ((`${args[0]}`.toUpperCase() === "GET") && (args[1] === "project.json")) { init(() => this.addEventListener('loadend', enhance)); // displaying loading indicator if not present already (as a mod) if (!document.getElementById('indicator')) { let _indicator = document.createElement('div'), NBSP = '\xA0'; _indicator.style = `position: fixed; top: 0; left: 0; z-index: 1000`; document.body.prepend(_indicator); this.addEventListener('progress', e => { _indicator.innerText = NBSP + "Loading data: " + (!e.total ? `${(e.loaded/1024**2).toFixed(1)} MB` : `${(100 * e.loaded / e.total).toFixed(2)}%`); }); this.addEventListener('loadend', () => {_indicator.innerText = ""}); } } return _open.apply(this, args); }; } }; init = (thunk=enhance) => {!init.done && (console.log("IntCyoaEnhancer!"), init.done = true, thunk())}; document.addEventListener('readystatechange', () => (document.readyState == 'complete') && ['activated', 'rows', 'pointTypes'].every(k => k in app.__vue__.$store.state.app) && init()); enhance = () => { let _lazy = thunk => {let result, cached = false; return () => (cached ? result : cached = true, result = thunk())}; let _try = (thunk, fallback) => {try {return thunk()} catch (e) {console.error(e); return fallback}}; let _prompt = (message, value, thunk) => {let s = prompt(message, value); return (s != null) && thunk(s)}; let range = n => Array.from({length: n}, (_, i) => i); let times = (n, f) => range(n).forEach(f); // title & savestate are stored in URL hash let _hash = _try(() => `["${decodeURIComponent( location.hash.slice(1) )}"]`); // it's a JSON array of 2 strings, without '["' & '"]' parts let $save = [], [$title="", $saved=""] = _try(() => JSON.parse(_hash), []); let $updateUrl = ({title=$title, save=$save}={}) => {location.hash = JSON.stringify([title, $saved=save.join(",")]).slice(2, -2)}; // app state accessors let $store = () => app.__vue__.$store, $state = () => $store().state.app; let $pointTypes = () => $state().pointTypes, $rows = () => $state().rows; let $items = _lazy(() => [].concat( ...$rows().map(row => row.objects) )); let $hiddenActive = _lazy(() => $items().filter(item => item.isSelectableMultiple || item.isImageUpload)); let $itemsMap = _lazy((m = new Map()) => ($items().forEach(item => m.set(item.id, item)), m)), $getItem = id => $itemsMap().get(id); try {$store()} catch (e) {throw Error("[IntCyoaEnhancer] Can't access app state!", {cause: e})} // logic taken from IntCyoaCreator as it appears to be hardwired into a UI component let _selectedMulti = (item, num) => { // selecting a multi-value let counter = 0, sign = Math.sign(num); let _timesCmp = n => (sign < 0 ? item.numMultipleTimesMinus < n : item.numMultipleTimesPluss > n); let _useMulti = () => _timesCmp(counter) && (item.multipleUseVariable = counter += sign, true); let _addPoints = () => $pointTypes().filter(points => points.id == item.multipleScoreId).every(points => _timesCmp(points.startingSum) && (item.multipleUseVariable = points.startingSum += sign, true)); times(Math.abs(num), _ => { if ((item.isMultipleUseVariable ? _useMulti() : _addPoints())) item.scores.forEach(score => $pointTypes().filter(points => points.id == score.id) .forEach(points => {points.startingSum -= sign * parseInt(score.value)})); }); }; let _loadSave = save => { // applying a savestate let _isHidden = s => s.includes("/ON#") || s.includes("/IMG#"); let tokens = save.split(','), activated = tokens.filter(s => !_isHidden(s)), hidden = tokens.filter(_isHidden); let _split = (sep, item, token, fn, [id, arg]=token.split(sep, 2)) => {(id == item.id) && fn(arg)}; $store().commit({type: 'cleanActivated'}); // hopefully not broken… $items().forEach(item => { if (item.isSelectableMultiple) hidden.forEach(token => _split("/ON#", item, token, num => _selectedMulti(item, parseInt(num)))); else if (item.isImageUpload) hidden.forEach(token => _split("/IMG#", item, token, img => {item.image = img.replaceAll("/CHAR#", ",")})); }); //$store().commit({type: 'addNewActivatedArray', newActivated: activated}); // not all versions have this :-( let _activated = new Set(activated), _isActivated = id => _activated.has(id); $state().activated = activated; $rows().forEach(row => { // yes, four-level nested loop is how the app does everything row.isEditModeOn = false; delete row.allowedChoicesChange; // bugfix: cleanActivated is supposed to do this… but it doesn't row.objects.filter(item => activated.includes(item.id)).forEach(item => { item.isActive = true; row.currentChoices += 1; item.scores.forEach(score => $pointTypes().filter(points => points.id == score.id).forEach(points => { if ((score.requireds.length <= 0) || $store().getters.checkRequireds(score)) { score.isActive = true; points.startingSum -= parseInt(score.value); } })); }); }); }; // these are used for generating savestate let _isActive = item => item.isActive || (item.isImageUpload && item.image) || (item.isSelectableMultiple && (item.multipleUseVariable !== 0)); let _activeId = item => (!_isActive(item) ? null : item.id + (item.isImageUpload ? `/IMG#${item.image.replaceAll(",", "/CHAR#")}` : item.isSelectableMultiple ? `/ON#${item.multipleUseVariable}` : "")); //let _activated = () => $items().map(_activeId).filter(Boolean); // this is how the app calculates it (selection order seems to be ignored) let _hiddenActivated = () => $hiddenActive().filter(_isActive).map(item => item.id); // images and multi-vals are excluded from state $store().watch(state => state.app.activated.filter(Boolean).concat( _hiddenActivated() ), // activated is formed incorrectly and may contain "" ids => {$save = ids.map($getItem).map(_activeId), $updateUrl()}); // compared to the app """optimization""" this is blazing fast let _bugfix = () => { $rows().forEach(row => {delete row.allowedChoicesChange}); // This is a runtime variable, why is it exported?! It breaks reset! }; // init && menu _bugfix(); let _title = document.title; $title && (document.title = $title); $saved && confirm("Load state from URL?") && _loadSave($saved); GM_registerMenuCommand("Change webpage title", () => _prompt("Change webpage title (empty to default)", $title||document.title, s => {document.title = ($title = s) || _title; $updateUrl()})); GM_registerMenuCommand("Edit state", () => _prompt("Edit state (empty to reset)", $saved, _loadSave)); }; })();