闪韵灵境谱面编辑器 模组加载器

一款《闪韵灵境》谱面编辑器的Mod加载器

  1. // ==UserScript==
  2. // @name BlitzRhythm Editor Mod Loader
  3. // @name:zh-CN 闪韵灵境谱面编辑器 模组加载器
  4. // @namespace cipher-editor-mods-loader
  5. // @version 1.1.4
  6. // @description A BlitzRhythm Editor Mod Loader
  7. // @description:zh-CN 一款《闪韵灵境》谱面编辑器的Mod加载器
  8. // @author Moyuer
  9. // @author:zh-CN 如梦Nya
  10. // @license MIT
  11. // @grant unsafeWindow
  12. // @grant GM_xmlhttpRequest
  13. // @connect beatsaver.com
  14. // @connect gitmirror.com
  15. // @connect githubusercontent.com
  16. // @match https://cipher-editor-cn.picovr.com/*
  17. // @match https://cipher-editor-va.picovr.com/*
  18. // @icon https://cipher-editor-va.picovr.com/favicon.ico
  19. // ==/UserScript==
  20.  
  21. let htmlSrc = "https://raw.githubusercontent.com/CMoyuer/BlitzRhythm-Editor-Mod-Loader/main/ModLoaderDrawer/dist/index.html"
  22.  
  23. if (getLanguage() === "zh")
  24. htmlSrc = "https://raw.gitmirror.com/CMoyuer/BlitzRhythm-Editor-Mod-Loader/main/ModLoaderDrawer/dist/index.html"
  25. if (GM_info.script.namespace.endsWith("-dev"))
  26. htmlSrc = "http://127.0.0.1"
  27.  
  28. /** @type {HTMLElement} */
  29. let modloaderBox
  30. /** @type {HTMLElement} */
  31. let divMask
  32. /** @type {HTMLElement} */
  33. let iframe
  34.  
  35. function initModloaderBox() {
  36. if (modloaderBox) return
  37. modloaderBox = document.createElement("div")
  38. modloaderBox.style = "position:absolute;width:100%;height:100%;top:0;left:0;z-index:9001;pointer-events:none;"
  39.  
  40. divMask = document.createElement("div")
  41. divMask.style = "width:100%;height:100%;background-color:#00000050;display:none;pointer-events:auto;"
  42. divMask.onclick = hideIframe
  43. modloaderBox.append(divMask)
  44.  
  45. iframe = document.createElement("iframe")
  46. modloaderBox.id = "modloaderIframe"
  47. iframe.style = "box-shadow: 0 0 10px 0 black;border:none;width:360px;height:100vh;position:fixed;right:0;top:0;bottom:0;transform:translateX(100%);z-index:9999;transition: transform 0.3s ease-in-out;pointer-events: auto;"
  48. let loadHtml = () => {
  49. let url = htmlSrc + "?t=" + new Date().getTime()
  50. console.log("ModLoader loading html from:", url)
  51. GM_xmlhttpRequest({
  52. url,
  53. method: "GET",
  54. timeout: 10 * 1000,
  55. onload: res => {
  56. iframe.srcdoc = res.response
  57. console.log("ModLoader load html success!")
  58. },
  59. onerror: res => {
  60. console.error("ModLoader load html failed:", res)
  61. setTimeout(loadHtml, 1000)
  62. },
  63. ontimeout: res => {
  64. console.error("ModLoader load html timeout")
  65. loadHtml()
  66. }
  67. })
  68. }
  69. loadHtml()
  70. modloaderBox.append(iframe)
  71. document.body.append(modloaderBox)
  72. }
  73.  
  74. function showIframe() {
  75. divMask.style.display = "block"
  76. iframe.style.transform = "translateX(0)"
  77. }
  78.  
  79. function hideIframe() {
  80. divMask.style.display = "none"
  81. iframe.style.transform = "translateX(100%)"
  82. }
  83.  
  84. function initShowButton() {
  85. let btnShow = document.createElement("div")
  86. btnShow.id = "btnModLoaderShow"
  87. btnShow.innerHTML = "M"
  88. btnShow.style = "position:absolute;transform:translate(-50%, -50%);left:calc(100vw - 50px);top:calc(100vh - 50px);width:50px;height: 50px;background-color:#2196F3;border-radius:25px;z-index:9000;font-size:1.5em;line-height:50px;text-align:center;color:white;font-family:Roboto,Helvetica,Arial,sans-serif;box-shadow: 0 0 6px 0 gray;user-select:none;"
  89. let info = {
  90. handle: 0,
  91. mousedown: false,
  92. dragging: false,
  93. canClick: true,
  94. rawPos: [0, 0],
  95. position: [0, 0],
  96. }
  97.  
  98. function getMoveDistance() {
  99. return Math.abs(info.position[0] - info.rawPos[0]) + Math.abs(info.position[1] - info.rawPos[1])
  100. }
  101.  
  102. btnShow.onmousedown = res => {
  103. btnShow.style.backgroundColor = "#1769AA"
  104. info.canClick = true
  105. info.mousedown = true
  106. info.handle = setTimeout(() => {
  107. btnShow.style.boxShadow = "0 0 6px 2px white"
  108. info.dragging = true
  109. info.handle = 0
  110. }, 100)
  111. if (btnShow.style.left && btnShow.style.left.startsWith("calc"))
  112. btnShow.style.left = btnShow.offsetLeft + "px"
  113. if (btnShow.style.top && btnShow.style.top.startsWith("calc"))
  114. btnShow.style.top = btnShow.offsetTop + "px"
  115. info.rawPos = [btnShow.offsetLeft, btnShow.offsetTop]
  116. info.position = [res.clientX, res.clientY]
  117. }
  118. btnShow.onmousemove = res => {
  119. if (!info.dragging) return
  120. let x = res.clientX
  121. let y = res.clientY
  122. let deltaX = x - info.position[0]
  123. let deltaY = y - info.position[1]
  124. let left = parseInt(btnShow.style.left || 0)
  125. let top = parseInt(btnShow.style.top || 0)
  126. btnShow.style.left = left + deltaX + 'px'
  127. btnShow.style.top = top + deltaY + 'px'
  128. info.position = [x, y]
  129. }
  130. btnShow.onmouseup = btnShow.onmouseleave = () => {
  131. btnShow.style.backgroundColor = "#2196F3"
  132. btnShow.style.boxShadow = "0 0 6px 0 gray"
  133. if (info.handle > 0) {
  134. clearTimeout(info.handle)
  135. info.handle = 0
  136. }
  137. info.canClick = !info.dragging
  138. info.mousedown = false
  139. info.dragging = false
  140. }
  141. btnShow.onclick = () => {
  142. if (!info.canClick) return
  143. showIframe()
  144. }
  145.  
  146. window.onresize = () => {
  147. let left = parseInt(btnShow.style.left || 0)
  148. let top = parseInt(btnShow.style.top || 0)
  149. if (window.innerWidth < left + 50)
  150. btnShow.style.left = "calc(100vw - 50px)"
  151. if (window.innerHeight < top + 50)
  152. btnShow.style.top = "calc(100vh - 50px)"
  153. }
  154.  
  155. document.body.appendChild(btnShow)
  156. }
  157.  
  158. function getLanguage() {
  159. let language = localStorage.getItem("i18nextLng") ?? "en"
  160. if (/^zh-?/.test(language)) language = "zh"
  161. return language
  162. }
  163.  
  164. (function () {
  165. 'use strict'
  166. initModloaderBox()
  167.  
  168. let handle = setInterval(() => {
  169. if (!unsafeWindow.modloader) return
  170. unsafeWindow.modloader.drawer = {
  171. methods: {
  172. show: showIframe,
  173. hide: hideIframe
  174. }
  175. }
  176. initShowButton()
  177. clearInterval(handle)
  178. }, 100)
  179. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址