FaustCore

Adds support for Faust mods

  1. // ==UserScript==
  2. // @name FaustCore
  3. // @version 2025-02-21
  4. // @description Adds support for Faust mods
  5. // @author Faust
  6. // @match https://esonline.su/
  7. // @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
  8. // @grant none
  9. // @namespace https://gf.qytechs.cn/users/1437749
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. "use strict";
  14. var MessageReason;
  15. (function (MessageReason) {
  16. MessageReason.user_join = "userJoin";
  17. MessageReason.user_leave = "userLeave";
  18. MessageReason.node_data = "nodeData";
  19. MessageReason.time_update = "serverTimecode";
  20. MessageReason.client_move = "clientMove";
  21. })(MessageReason || (MessageReason = {}));
  22. function getScreenResolution() {
  23. const resolutions = [
  24. [1920, 1080],
  25. [1792, 1008],
  26. [1664, 936],
  27. [1536, 864],
  28. [1408, 792],
  29. [1280, 720],
  30. [1152, 648],
  31. [1024, 576],
  32. [896, 504],
  33. [768, 432],
  34. [640, 360],
  35. [512, 288],
  36. [384, 216],
  37. ];
  38. const screen_width = window.innerWidth;
  39. const screen_height = window.innerHeight;
  40. let selected_resolution;
  41. for (let index in resolutions) {
  42. let resolution = resolutions[index];
  43. if (resolution[0] <= screen_width && resolution[1] <= screen_height) {
  44. selected_resolution = resolution;
  45. break;
  46. }
  47. }
  48. if (!selected_resolution) {
  49. console.error("screen too small " + screen_width + "x" + screen_height);
  50. selected_resolution = resolutions[resolutions.length - 1];
  51. }
  52. return selected_resolution;
  53. }
  54. function display_notification(text) {
  55. const elements = document.querySelectorAll("#notify");
  56. if (elements.length > 0 && elements[0].parentElement) {
  57. elements[0].parentElement.removeChild(elements[0]);
  58. }
  59. const text_bubble = document.createElement("div");
  60. text_bubble.id = "notify";
  61. text_bubble.innerHTML = text;
  62. text_bubble.style.animationName = "hide";
  63. text_bubble.style.animationDelay = "2s";
  64. text_bubble.style.animationDuration = "0.5s";
  65. text_bubble.style.animationFillMode = "forwards";
  66. text_bubble.style.zIndex = "9999";
  67. document.body.appendChild(text_bubble);
  68. text_bubble.addEventListener("animationend", () => {
  69. text_bubble.remove();
  70. });
  71. }
  72. class AbstractMenuItem {
  73. constructor() {
  74. this.is_added = false;
  75. this.is_hidden = false;
  76. this.main_div = document.createElement("div");
  77. }
  78. addElement() {
  79. if (this.is_added) {
  80. return;
  81. }
  82. this.main_div = this.composeMainDiv();
  83. document.body.appendChild(this.main_div);
  84. this.is_added = true;
  85. }
  86. removeElement() {
  87. if (!this.is_added) {
  88. return;
  89. }
  90. this.main_div.remove();
  91. this.is_added = false;
  92. }
  93. hideElement() {
  94. if (!this.is_added) {
  95. return;
  96. }
  97. this.main_div.style.display = "none";
  98. this.is_hidden = true;
  99. }
  100. unhideElement() {
  101. if (!this.is_added) {
  102. return;
  103. }
  104. this.main_div.style.display = "block";
  105. this.is_hidden = false;
  106. }
  107. isShown() {
  108. return this.is_added;
  109. }
  110. isHidden() {
  111. return this.is_hidden;
  112. }
  113. }
  114. class MenuButtonsFactory {
  115. static buildButton(text, on_click) {
  116. const button = document.createElement("button");
  117. button.style.borderRadius = "10px";
  118. button.style.border = "2px solid rgba(3, 55, 170, 0.8)";
  119. button.style.backgroundColor = "rgba(3, 55, 170, 0.8)";
  120. button.style.color = "white";
  121. button.style.padding = "5px 10px";
  122. button.style.fontSize = "14px";
  123. button.innerText = text;
  124. button.addEventListener("mouseenter", () => {
  125. button.style.backgroundColor = "white";
  126. button.style.color = "rgba(3, 55, 170, 0.8)";
  127. });
  128. button.addEventListener("mouseleave", () => {
  129. button.style.backgroundColor = "rgba(3, 55, 170, 0.8)";
  130. button.style.color = "white";
  131. });
  132. button.addEventListener("mousedown", on_click);
  133. return button;
  134. }
  135. }
  136. class TweakMenu extends AbstractMenuItem {
  137. constructor() {
  138. super();
  139. }
  140. getScrollableArea(screen_height) {
  141. const scrollableDiv = document.createElement("div");
  142. scrollableDiv.style.height = (screen_height / 3).toString() + "px";
  143. scrollableDiv.style.overflow = "auto";
  144. scrollableDiv.style.border = "1px solid #ccc";
  145. scrollableDiv.style.padding = "10px";
  146. scrollableDiv.style.backgroundColor = "rgba(0, 0, 0, 0.1)";
  147. scrollableDiv.style.borderRadius = "10px";
  148. scrollableDiv.style.marginBottom = "20px";
  149. return scrollableDiv;
  150. }
  151. getTweakNameColor(tweak) {
  152. if (tweak.isEnabled()) {
  153. return "green";
  154. }
  155. return "white";
  156. }
  157. buildDivUponTweak() {
  158. const result = [];
  159. for (let iter = 0; iter < loaded_tweaks.length; iter++) {
  160. const entry = document.createElement("div");
  161. entry.style.display = "flex";
  162. entry.style.flexDirection = "row";
  163. entry.style.alignItems = "center";
  164. entry.style.justifyContent = "flex-start";
  165. entry.style.borderRadius = "6px";
  166. entry.style.border = "2px solid rgba(230, 224, 203, 0.6)";
  167. const checkbox = document.createElement("input");
  168. checkbox.type = "checkbox";
  169. checkbox.id = loaded_tweaks[iter].getTweakName();
  170. checkbox.style.marginLeft = "10px";
  171. checkbox.style.marginRight = "10px";
  172. checkbox.checked = loaded_tweaks[iter].isEnabled();
  173. checkbox.addEventListener("change", (event) => {
  174. const target = event.target;
  175. target.checked ? loaded_tweaks[iter].enable() : loaded_tweaks[iter].disable();
  176. });
  177. const label = document.createElement("label");
  178. label.htmlFor = checkbox.id;
  179. label.innerText = checkbox.id;
  180. label.style.fontSize = "21px";
  181. label.style.color = this.getTweakNameColor(loaded_tweaks[iter]);
  182. entry.appendChild(checkbox);
  183. entry.appendChild(label);
  184. result.push(entry);
  185. }
  186. return result;
  187. }
  188. composeMainDiv() {
  189. const result = document.createElement("div");
  190. // resizing
  191. const resolution = getScreenResolution();
  192. result.style.position = "fixed";
  193. result.style.width = (resolution[0] / 2.5).toString() + "px";
  194. result.style.height = "fit-content";
  195. // Centering block
  196. result.style.top = "50%";
  197. result.style.left = "50%";
  198. result.style.transform = "translate(-50%, -50%)";
  199. // Cosmetics
  200. result.style.padding = "20px";
  201. result.style.backgroundColor = "rgba(0, 0, 0, 0.75)"; // transparent black
  202. result.style.borderRadius = "10px";
  203. result.style.textAlign = "center";
  204. result.style.backdropFilter = "blur(5px)";
  205. result.style.zIndex = "9999";
  206. const window_name = document.createElement("h2");
  207. window_name.style.color = "white";
  208. window_name.style.marginBottom = "20px";
  209. window_name.innerText = "Меню твиков";
  210. // TODO: add macros for resolution results (or even better, return a struct)
  211. const scroll_area = this.getScrollableArea(resolution[1]);
  212. const div_tweaks = this.buildDivUponTweak();
  213. for (let iter = 0; iter < div_tweaks.length; iter++) {
  214. scroll_area.appendChild(div_tweaks[iter]);
  215. }
  216. result.appendChild(window_name);
  217. result.appendChild(scroll_area);
  218. result.appendChild(MenuButtonsFactory.buildButton("Close", () => this.removeElement()));
  219. return result;
  220. }
  221. }
  222. class GratitudeMenu extends AbstractMenuItem {
  223. constructor() {
  224. super();
  225. }
  226. getScrollableArea(screen_height) {
  227. const scrollableDiv = document.createElement("div");
  228. scrollableDiv.style.height = (screen_height / 4).toString() + "px";
  229. scrollableDiv.style.overflow = "auto";
  230. scrollableDiv.style.border = "1px solid #ccc";
  231. scrollableDiv.style.padding = "10px";
  232. scrollableDiv.style.backgroundColor = "rgba(0, 0, 0, 0.1)";
  233. scrollableDiv.style.borderRadius = "10px";
  234. scrollableDiv.style.marginBottom = "20px";
  235. return scrollableDiv;
  236. }
  237. composeMainDiv() {
  238. const result = document.createElement("div");
  239. // resizing
  240. const resolution = getScreenResolution();
  241. result.style.position = "fixed";
  242. result.style.width = (resolution[0] / 3).toString() + "px";
  243. result.style.height = "fit-content";
  244. // Centering block
  245. result.style.top = "50%";
  246. result.style.left = "50%";
  247. result.style.transform = "translate(-50%, -50%)";
  248. // Cosmetics
  249. result.style.padding = "20px";
  250. result.style.backgroundColor = "rgba(0, 0, 0, 0.75)"; // transparent black
  251. result.style.borderRadius = "10px";
  252. result.style.textAlign = "center";
  253. result.style.backdropFilter = "blur(5px)";
  254. result.style.zIndex = "9999";
  255. const window_name = document.createElement("h2");
  256. window_name.style.color = "white";
  257. window_name.style.marginBottom = "20px";
  258. window_name.innerText = "Благодарности";
  259. const scroll_area = this.getScrollableArea(resolution[1]);
  260. const gratitude_block = document.createElement("div");
  261. gratitude_block.style.color = "white";
  262. gratitude_block.style.fontSize = "16px";
  263. gratitude_block.innerText =
  264. "Спасибо тебе, игрок, за то что даёшь проекту шанс на жизнь.\nТак же спасибо ребятами ждущим альфа-тест сюжетного твика. Ваша поддержка заставляла проект жить.\n\n Спасибо, TRIOLA.\nСпасибо, makter.";
  265. result.appendChild(window_name);
  266. result.appendChild(scroll_area);
  267. scroll_area.appendChild(gratitude_block);
  268. result.appendChild(MenuButtonsFactory.buildButton("Close", () => this.removeElement()));
  269. return result;
  270. }
  271. }
  272. /// <reference path="./menu_interface.ts" />
  273. /// <reference path="../menu_button_factory.ts" />
  274. /// <reference path="./tweaks_menu.ts" />
  275. /// <reference path="./gratitude_menu.ts" />
  276. class FaustCoreMenu extends AbstractMenuItem {
  277. constructor() {
  278. super();
  279. this.tweak_menu = new TweakMenu();
  280. this.gratitude_menu = new GratitudeMenu();
  281. }
  282. composeMainDiv() {
  283. const result = document.createElement("div");
  284. // positioning
  285. result.style.position = "absolute";
  286. result.style.left = "50%";
  287. result.style.top = "5%";
  288. result.style.transform = "translate(-50%, -50%)";
  289. // reshaping
  290. result.style.width = "fit-content";
  291. result.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
  292. result.style.color = "white";
  293. result.style.zIndex = "9990";
  294. result.style.borderRadius = "10px";
  295. result.style.textAlign = "center";
  296. result.style.backdropFilter = "blur(5px)";
  297. // element positioning
  298. result.style.display = "flex";
  299. result.style.flexDirection = "row";
  300. result.style.alignItems = "center";
  301. result.style.justifyContent = "flex-start";
  302. result.style.gap = "20px";
  303. result.style.padding = "10px";
  304. const window_name = document.createElement("h2");
  305. window_name.style.fontWeight = "bold";
  306. window_name.style.color = "white";
  307. window_name.innerText = "Faust Core";
  308. result.appendChild(window_name);
  309. result.appendChild(MenuButtonsFactory.buildButton("Твики", () => this.tweak_menu.addElement()));
  310. result.appendChild(MenuButtonsFactory.buildButton("?", () => this.gratitude_menu.addElement()));
  311. return result;
  312. }
  313. }
  314. /// <reference path="./menu_windows/faust_core_menu.ts" />
  315. class Application {
  316. constructor() {
  317. this.fcore_menu = new FaustCoreMenu();
  318. this.fcore_menu.addElement();
  319. }
  320. }
  321. const app = new Application();
  322. const OriginalWebSocket = window.WebSocket; // backup original websocket
  323. var WebsocketListeners;
  324. (function (WebsocketListeners) {
  325. let receive_listeners = new Map();
  326. let send_listeners = new Map();
  327. function addReceiveListener(listener) {
  328. if (receive_listeners.has(listener.getTweakName())) {
  329. return false;
  330. }
  331. receive_listeners.set(listener.getTweakName(), listener);
  332. return true;
  333. }
  334. WebsocketListeners.addReceiveListener = addReceiveListener;
  335. function removeReceiveListener(tweak_name) {
  336. if (typeof tweak_name !== "string") {
  337. tweak_name = tweak_name.getTweakName();
  338. }
  339. receive_listeners.delete(tweak_name);
  340. }
  341. WebsocketListeners.removeReceiveListener = removeReceiveListener;
  342. function addSendListener(listener) {
  343. if (send_listeners.has(listener.getTweakName())) {
  344. return false;
  345. }
  346. send_listeners.set(listener.getTweakName(), listener);
  347. return true;
  348. }
  349. WebsocketListeners.addSendListener = addSendListener;
  350. function removeSendListener(tweak_name) {
  351. if (typeof tweak_name !== "string") {
  352. tweak_name = tweak_name.getTweakName();
  353. }
  354. send_listeners.delete(tweak_name);
  355. }
  356. WebsocketListeners.removeSendListener = removeSendListener;
  357. function triggerReceiveListener(data) {
  358. for (let [_, value] of receive_listeners) {
  359. value.processReceivedWebsocketMessage(data);
  360. }
  361. }
  362. WebsocketListeners.triggerReceiveListener = triggerReceiveListener;
  363. function triggerSendListener(data) {
  364. for (let [_, value] of send_listeners) {
  365. value.processSendedWebsocketMessage(data);
  366. }
  367. }
  368. WebsocketListeners.triggerSendListener = triggerSendListener;
  369. })(WebsocketListeners || (WebsocketListeners = {}));
  370. class PatchedWebSocket extends OriginalWebSocket {
  371. constructor(url, protocols) {
  372. super(url, protocols);
  373. // redefining send method
  374. const originalSend = this.send;
  375. this.send = (data) => {
  376. originalSend.call(this, data);
  377. WebsocketListeners.triggerSendListener(data);
  378. };
  379. // redefining accept method
  380. this.addEventListener("message", (event) => {
  381. WebsocketListeners.triggerReceiveListener(event.data);
  382. });
  383. }
  384. }
  385. // Redefining global websocket with Faust patch
  386. window.WebSocket = PatchedWebSocket;
  387. Object.defineProperties(window.WebSocket, {
  388. CONNECTING: { value: OriginalWebSocket.CONNECTING },
  389. OPEN: { value: OriginalWebSocket.OPEN },
  390. CLOSING: { value: OriginalWebSocket.CLOSING },
  391. CLOSED: { value: OriginalWebSocket.CLOSED },
  392. });
  393. const loaded_tweaks = [];
  394. class Tweaks {
  395. constructor() {
  396. loaded_tweaks.push(this);
  397. }
  398. getTweakName() {
  399. return this.name;
  400. }
  401. isServerSide() {
  402. return this.sever_side;
  403. }
  404. isEnabled() {
  405. return this.is_enabled;
  406. }
  407. }
  408. /// <reference path="./tweak_interface.ts" />
  409. class PlayerLister extends Tweaks {
  410. constructor() {
  411. super();
  412. this.name = "Отображать меню с игроками на локации";
  413. this.sever_side = false;
  414. this.is_enabled = false;
  415. // Init main div
  416. this.main_div = this.initPlayerListBlock();
  417. this.main_div.style.display = "none"; // Hiding window
  418. document.body.appendChild(this.main_div);
  419. // sub div will remain empty
  420. this.player_list_div = document.createElement("div");
  421. this.player_list_div.style.padding = "20px";
  422. this.main_div.appendChild(this.player_list_div);
  423. this.is_dragging = false;
  424. this.offset_x = 0;
  425. this.offset_y = 0;
  426. this.players_on_location = new Map();
  427. }
  428. enable() {
  429. this.is_enabled = true;
  430. this.main_div.style.display = "block";
  431. WebsocketListeners.addReceiveListener(this);
  432. display_notification("Для обновления списка перезайдите на локацию.");
  433. }
  434. disable() {
  435. this.is_enabled = false;
  436. this.main_div.style.display = "none";
  437. WebsocketListeners.removeReceiveListener(this);
  438. // Clearing info
  439. this.players_on_location.clear();
  440. this.updatePlayerList();
  441. }
  442. /**
  443. * @brief Parses user json entry from websocket
  444. *
  445. * @param user json info
  446. * @return PlayerInfo: parsed data
  447. */
  448. parseSocketUserData(user) {
  449. const result = {
  450. id: user.id,
  451. name: user.name,
  452. color: user.color,
  453. };
  454. return result;
  455. }
  456. processReceivedWebsocketMessage(websocket_event_data) {
  457. try {
  458. const parsedData = JSON.parse(websocket_event_data);
  459. switch (parsedData.reason) {
  460. case MessageReason.user_join:
  461. const user = this.parseSocketUserData(parsedData.user);
  462. this.players_on_location.set(user.id, user);
  463. break;
  464. case MessageReason.node_data:
  465. this.players_on_location.clear();
  466. const users = parsedData.users;
  467. if (!Array.isArray(users)) {
  468. return;
  469. }
  470. users.forEach((user) => {
  471. user = this.parseSocketUserData(user);
  472. this.players_on_location.set(user.id, user);
  473. });
  474. break;
  475. case MessageReason.user_leave:
  476. this.players_on_location.delete(parsedData.id);
  477. break;
  478. default:
  479. return;
  480. }
  481. this.updatePlayerList();
  482. }
  483. catch (error) {
  484. console.error(error);
  485. }
  486. }
  487. processSendedWebsocketMessage(websocket_event_data) { }
  488. updatePlayerList() {
  489. this.player_list_div.innerHTML = ""; // Clearing list
  490. for (const [key, value] of this.players_on_location) {
  491. const player_entry = document.createElement("p");
  492. player_entry.style.color = "#" + value.color;
  493. player_entry.innerText = key.toString() + " " + value.name;
  494. // player_entry.style.marginLeft = "10px";
  495. this.player_list_div.appendChild(player_entry);
  496. }
  497. }
  498. initPlayerListBlock() {
  499. const movableDiv = document.createElement("div");
  500. movableDiv.style.position = "fixed";
  501. movableDiv.style.top = "100px";
  502. movableDiv.style.left = "100px";
  503. movableDiv.style.width = "fit-content";
  504. movableDiv.style.height = "fit-content";
  505. movableDiv.style.backdropFilter = "blur(5px)";
  506. movableDiv.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
  507. movableDiv.style.cursor = "grab";
  508. movableDiv.style.borderRadius = "10px";
  509. movableDiv.style.zIndex = "9990";
  510. const window_name = document.createElement("h3");
  511. window_name.style.color = "white";
  512. window_name.innerText = "На локации:";
  513. window_name.style.marginLeft = "5px";
  514. window_name.style.marginRight = "5px";
  515. window_name.style.marginTop = "5px";
  516. movableDiv.appendChild(window_name);
  517. // Enabling element moving
  518. movableDiv.addEventListener("mousedown", (event) => {
  519. this.is_dragging = true;
  520. this.offset_x = event.clientX - movableDiv.getBoundingClientRect().left;
  521. this.offset_y = event.clientY - movableDiv.getBoundingClientRect().top;
  522. // removing selection while holding
  523. event.preventDefault();
  524. });
  525. document.addEventListener("mousemove", (event) => {
  526. if (this.is_dragging) {
  527. movableDiv.style.left = `${event.clientX - this.offset_x}px`;
  528. movableDiv.style.top = `${event.clientY - this.offset_y}px`;
  529. }
  530. });
  531. document.addEventListener("mouseup", () => {
  532. this.is_dragging = false;
  533. });
  534. // Adding element to page
  535. document.body.appendChild(movableDiv);
  536. return movableDiv;
  537. }
  538. }
  539. //TODO: must be a better way of doing this
  540. const player_lister_tweak = new PlayerLister();
  541. //# sourceMappingURL=main.js.map
  542. })();

QingJ © 2025

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