IdlePixel+

Idle-Pixel plugin framework

当前为 2022-03-25 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/441206/1031967/IdlePixel%2B.js

  1. // ==UserScript==
  2. // @name IdlePixel+
  3. // @namespace com.anwinity.idlepixel
  4. // @version 0.0.7
  5. // @description Idle-Pixel plugin framework
  6. // @author Anwinity
  7. // @match https://idle-pixel.com/play.php*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. const CONFIG_TYPES_LABEL = ["label"];
  15. const CONFIG_TYPES_BOOLEAN = ["boolean", "bool", "checkbox"];
  16. const CONFIG_TYPES_INTEGER = ["integer", "int"];
  17. const CONFIG_TYPES_FLOAT = ["number", "num", "float"];
  18. const CONFIG_TYPES_STRING = ["string", "text"];
  19. const CONFIG_TYPES_SELECT = ["select"];
  20. const CONFIG_TYPES_COLOR = ["color"];
  21.  
  22. const INFO = {
  23. ores: ["stone", "copper", "iron", "silver", "gold"],
  24. smeltableOres: ["copper", "iron", "silver", "gold"],
  25. smeltOilCost: {
  26. copper: 1,
  27. iron: 5,
  28. silver: 20,
  29. gold: 100
  30. },
  31. bars: ["bronze_bar", "iron_bar", "silver_bar", "gold_bar"],
  32. seeds: ["dotted_green_leaf_seeds", "green_leaf_seeds", "lime_leaf_seeds", "red_mushroom_seeds"],
  33. combatZones: ["field","forest","cave"],
  34. combatZoneBaseEnergyCost: {
  35. field: 50,
  36. forest: 200,
  37. cave: 500
  38. },
  39. spellManaCost: {
  40. heal: 2
  41. }
  42. };
  43.  
  44. if(window.IdlePixelPlus) {
  45. console.error(`Another version of IdlePixelPlus (v${window.IdlePixelPlus.version}) is already loaded. This may cause conflicts for some plugins.`);
  46. return;
  47. }
  48.  
  49. class IdlePixelPlusPlugin {
  50.  
  51. constructor(id, opts) {
  52. if(typeof id !== "string") {
  53. throw new TypeError("IdlePixelPlusPlugin constructor takes the following arguments: (id:string, opts?:object)");
  54. }
  55. this.id = id;
  56. this.opts = opts || {};
  57. this.config = null;
  58. }
  59.  
  60. getConfig(name) {
  61. if(!this.config) {
  62. IdlePixelPlus.loadPluginConfigs(this.id);
  63. }
  64. if(this.config) {
  65. return this.config[name];
  66. }
  67. }
  68.  
  69. /*
  70. onConfigsChanged() { }
  71. onLogin() { }
  72. onMessageReceived(data) { }
  73. onVariableSet(key, valueBefore, valueAfter) { }
  74. onChat(data) { }
  75. onPanelChanged(panelBefore, panelAfter) { }
  76. onCombatStart() { }
  77. onCombatEnd() { }
  78. */
  79.  
  80. }
  81.  
  82. const internal = {
  83. init() {
  84. const self = this;
  85.  
  86. // hook into websocket messages
  87. const original_open_websocket = window.open_websocket;
  88. window.open_websocket = function() {
  89. original_open_websocket.apply(this, arguments);
  90. const original_onmessage = window.websocket.websocket.onmessage;
  91. window.websocket.websocket.onmessage = function(event) {
  92. original_onmessage.apply(window.websocket.websocket, arguments);
  93. self.onMessageReceived(event.data);
  94. }
  95. }
  96.  
  97. // hook into Items.set, which is where var_ values are set
  98. const original_items_set = Items.set;
  99. Items.set = function(key, value) {
  100. let valueBefore = window["var_"+key];
  101. original_items_set.apply(this, arguments);
  102. let valueAfter = window["var_"+key];
  103. self.onVariableSet(key, valueBefore, valueAfter);
  104. }
  105.  
  106. // hook into switch_panels, which is called when the main panel is changed. This is also used for custom panels.
  107. const original_switch_panels = window.switch_panels;
  108. window.switch_panels = function(id) {
  109. let panelBefore = Globals.currentPanel;
  110. if(panelBefore && panelBefore.startsWith("panel-")) {
  111. panelBefore = panelBefore.substring("panel-".length);
  112. }
  113. self.hideCustomPanels();
  114. original_switch_panels.apply(this, arguments);
  115. let panelAfter = Globals.currentPanel;
  116. if(panelAfter && panelAfter.startsWith("panel-")) {
  117. panelAfter = panelAfter.substring("panel-".length);
  118. }
  119. self.onPanelChanged(panelBefore, panelAfter);
  120. }
  121.  
  122. // create plugin menu item and panel
  123. const lastMenuItem = $("#menu-bar-buttons > .hover-menu-bar-item").last();
  124. lastMenuItem.after(`
  125. <div onclick="IdlePixelPlus.setPanel('idlepixelplus')" class="hover hover-menu-bar-item">
  126. <img id="menu-bar-idlepixelplus-icon" src="https://anwinity.com/idlepixelplus/plugins.png"> PLUGINS
  127. </div>
  128. `);
  129. self.addPanel("idlepixelplus", "IdlePixel+ Plugins", function() {
  130. let content = `
  131. <style>
  132. .idlepixelplus-plugin-box {
  133. display: block;
  134. position: relative;
  135. padding: 0.25em;
  136. color: white;
  137. background-color: rgb(107, 107, 107);
  138. border: 1px solid black;
  139. border-radius: 6px;
  140. margin-bottom: 0.5em;
  141. }
  142. .idlepixelplus-plugin-box .idlepixelplus-plugin-settings-button {
  143. position: absolute;
  144. right: 2px;
  145. top: 2px;
  146. cursor: pointer;
  147. }
  148. .idlepixelplus-plugin-box .idlepixelplus-plugin-config-section {
  149. display: grid;
  150. grid-template-columns: minmax(100px, min-content) 1fr;
  151. row-gap: 0.5em;
  152. column-gap: 0.5em;
  153. white-space: nowrap;
  154. }
  155. </style>
  156. `;
  157. self.forEachPlugin(plugin => {
  158. let id = plugin.id;
  159. let name = "An IdlePixel+ Plugin!";
  160. let description = "";
  161. let author = "unknown";
  162. if(plugin.opts.about) {
  163. let about = plugin.opts.about;
  164. name = about.name || name;
  165. description = about.description || description;
  166. author = about.author || author;
  167. }
  168. content += `
  169. <div id="idlepixelplus-plugin-box-${id}" class="idlepixelplus-plugin-box">
  170. <strong><u>${name||id}</u></strong> (by ${author})<br />
  171. <span>${description}</span><br />
  172. <div class="idlepixelplus-plugin-config-section" style="display: none">
  173. <hr style="grid-column: span 2">
  174. `;
  175. if(plugin.opts.config && Array.isArray(plugin.opts.config)) {
  176. plugin.opts.config.forEach(cfg => {
  177. if(CONFIG_TYPES_LABEL.includes(cfg.type)) {
  178. content += `<h5 style="grid-column: span 2; margin-bottom: 0; font-weight: 600">${cfg.label}</h5>`;
  179. }
  180. else if(CONFIG_TYPES_BOOLEAN.includes(cfg.type)) {
  181. content += `
  182. <div>
  183. <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
  184. </div>
  185. <div>
  186. <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="checkbox" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" />
  187. </div>
  188. `;
  189. }
  190. else if(CONFIG_TYPES_INTEGER.includes(cfg.type)) {
  191. content += `
  192. <div>
  193. <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
  194. </div>
  195. <div>
  196. <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="number" step="1" min="${cfg.min || ''}" max="${cfg.max || ''}" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" />
  197. </div>
  198. `;
  199. }
  200. else if(CONFIG_TYPES_FLOAT.includes(cfg.type)) {
  201. content += `
  202. <div>
  203. <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
  204. </div>
  205. <div>
  206. <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="number" step="${cfg.step || ''}" min="${cfg.min || ''}" max="${cfg.max || ''}" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" />
  207. </div>
  208. `;
  209. }
  210. else if(CONFIG_TYPES_STRING.includes(cfg.type)) {
  211. content += `
  212. <div>
  213. <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
  214. </div>
  215. <div>
  216. <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="text" maxlength="${cfg.max || ''}" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" />
  217. </div>
  218. `;
  219. }
  220. else if(CONFIG_TYPES_COLOR.includes(cfg.type)) {
  221. content += `
  222. <div>
  223. <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
  224. </div>
  225. <div>
  226. <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="color" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" />
  227. </div>
  228. `;
  229. }
  230. else if(CONFIG_TYPES_SELECT.includes(cfg.type)) {
  231. content += `
  232. <div>
  233. <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
  234. </div>
  235. <div>
  236. <select id="idlepixelplus-config-${plugin.id}-${cfg.id}" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)">
  237. `;
  238. if(cfg.options && Array.isArray(cfg.options)) {
  239. cfg.options.forEach(option => {
  240. if(typeof option === "string") {
  241. content += `<option value="${option}">${option}</option>`;
  242. }
  243. else {
  244. content += `<option value="${option.value}">${option.label || option.value}</option>`;
  245. }
  246. });
  247. }
  248. content += `
  249. </select>
  250. </div>
  251. `;
  252. }
  253. });
  254. content += `
  255. <div style="grid-column: span 2">
  256. <button id="idlepixelplus-configbutton-${plugin.id}-reload" onclick="IdlePixelPlus.loadPluginConfigs('${id}')">Reload</button>
  257. <button id="idlepixelplus-configbutton-${plugin.id}-apply" onclick="IdlePixelPlus.savePluginConfigs('${id}')">Apply</button>
  258. </div>
  259. `;
  260. }
  261. content += "</div>";
  262. if(plugin.opts.config) {
  263. content += `
  264. <div class="idlepixelplus-plugin-settings-button">
  265. <button onclick="$('#idlepixelplus-plugin-box-${id} .idlepixelplus-plugin-config-section').toggle()">Settings</button>
  266. </div>`;
  267. }
  268. content += "</div>";
  269. });
  270.  
  271. return content;
  272. });
  273.  
  274. console.log(`IdlePixelPlus (v${self.version}) initialized.`);
  275. }
  276. };
  277.  
  278. class IdlePixelPlus {
  279.  
  280. constructor() {
  281. this.version = GM_info.script.version;
  282. this.plugins = {};
  283. this.panels = {};
  284. this.debug = false;
  285. this.info = INFO;
  286. }
  287.  
  288. getVar(name, type) {
  289. let s = window[`var_${name}`];
  290. if(type) {
  291. switch(type) {
  292. case "int":
  293. case "integer":
  294. return parseInt(s);
  295. case "number":
  296. case "float":
  297. return parseFloat(s);
  298. case "boolean":
  299. case "bool":
  300. if(s=="true") return true;
  301. if(s=="false") return false;
  302. return undefined;
  303. }
  304. }
  305. return s;
  306. }
  307.  
  308. getVarOrDefault(name, defaultValue, type) {
  309. let s = window[`var_${name}`];
  310. if(s==null || typeof s === "undefined") {
  311. return defaultValue;
  312. }
  313. if(type) {
  314. let value;
  315. switch(type) {
  316. case "int":
  317. case "integer":
  318. value = parseInt(s);
  319. return isNaN(value) ? defaultValue : value;
  320. case "number":
  321. case "float":
  322. value = parseFloat(s);
  323. return isNaN(value) ? defaultValue : value;
  324. case "boolean":
  325. case "bool":
  326. if(s=="true") return true;
  327. if(s=="false") return false;
  328. return defaultValue;
  329. }
  330. }
  331. return s;
  332. }
  333.  
  334. setPluginConfigUIDirty(id, dirty) {
  335. if(typeof id !== "string" || typeof dirty !== "boolean") {
  336. throw new TypeError("IdlePixelPlus.setPluginConfigUIDirty takes the following arguments: (id:string, dirty:boolean)");
  337. }
  338. const plugin = this.plugins[id];
  339. const button = $(`#idlepixelplus-configbutton-${plugin.id}-apply`);
  340. if(button) {
  341. button.prop("disabled", !(dirty));
  342. }
  343. }
  344.  
  345. loadPluginConfigs(id) {
  346. if(typeof id !== "string") {
  347. throw new TypeError("IdlePixelPlus.reloadPluginConfigs takes the following arguments: (id:string)");
  348. }
  349. const plugin = this.plugins[id];
  350. const config = {};
  351. let stored;
  352. try {
  353. stored = JSON.parse(localStorage.getItem(`idlepixelplus.${id}.config`) || "{}");
  354. }
  355. catch(err) {
  356. console.error(`Failed to load configs for plugin with id "${id} - will use defaults instead."`);
  357. stored = {};
  358. }
  359. if(plugin.opts.config && Array.isArray(plugin.opts.config)) {
  360. plugin.opts.config.forEach(cfg => {
  361. const el = $(`#idlepixelplus-config-${plugin.id}-${cfg.id}`);
  362. let value = stored[cfg.id];
  363. if(value==null || typeof value === "undefined") {
  364. value = cfg.default;
  365. }
  366. config[cfg.id] = value;
  367.  
  368. if(el) {
  369. if(CONFIG_TYPES_BOOLEAN.includes(cfg.type) && typeof value === "boolean") {
  370. el.prop("checked", value);
  371. }
  372. else if(CONFIG_TYPES_INTEGER.includes(cfg.type) && typeof value === "number") {
  373. el.val(value);
  374. }
  375. else if(CONFIG_TYPES_FLOAT.includes(cfg.type) && typeof value === "number") {
  376. el.val(value);
  377. }
  378. else if(CONFIG_TYPES_STRING.includes(cfg.type) && typeof value === "string") {
  379. el.val(value);
  380. }
  381. else if(CONFIG_TYPES_SELECT.includes(cfg.type) && typeof value === "string") {
  382. el.val(value);
  383. }
  384. else if(CONFIG_TYPES_COLOR.includes(cfg.type) && typeof value === "string") {
  385. el.val(value);
  386. }
  387. }
  388. });
  389. }
  390. plugin.config = config;
  391. this.setPluginConfigUIDirty(id, false);
  392. if(typeof plugin.onConfigsChanged === "function") {
  393. plugin.onConfigsChanged();
  394. }
  395. }
  396.  
  397. savePluginConfigs(id) {
  398. if(typeof id !== "string") {
  399. throw new TypeError("IdlePixelPlus.savePluginConfigs takes the following arguments: (id:string)");
  400. }
  401. const plugin = this.plugins[id];
  402. const config = {};
  403. if(plugin.opts.config && Array.isArray(plugin.opts.config)) {
  404. plugin.opts.config.forEach(cfg => {
  405. const el = $(`#idlepixelplus-config-${plugin.id}-${cfg.id}`);
  406. let value;
  407. if(CONFIG_TYPES_BOOLEAN.includes(cfg.type)) {
  408. config[cfg.id] = el.is(":checked");
  409. }
  410. else if(CONFIG_TYPES_INTEGER.includes(cfg.type)) {
  411. config[cfg.id] = parseInt(el.val());
  412. }
  413. else if(CONFIG_TYPES_FLOAT.includes(cfg.type)) {
  414. config[cfg.id] = parseFloat(el.val());
  415. }
  416. else if(CONFIG_TYPES_STRING.includes(cfg.type)) {
  417. config[cfg.id] = el.val();
  418. }
  419. else if(CONFIG_TYPES_SELECT.includes(cfg.type)) {
  420. config[cfg.id] = el.val();
  421. }
  422. else if(CONFIG_TYPES_COLOR.includes(cfg.type)) {
  423. config[cfg.id] = el.val();
  424. }
  425. });
  426. }
  427. plugin.config = config;
  428. localStorage.setItem(`idlepixelplus.${id}.config`, JSON.stringify(config));
  429. this.setPluginConfigUIDirty(id, false);
  430. if(typeof plugin.onConfigsChanged === "function") {
  431. plugin.onConfigsChanged();
  432. }
  433. }
  434.  
  435. addPanel(id, title, content) {
  436. if(typeof id !== "string" || typeof title !== "string" || (typeof content !== "string" && typeof content !== "function") ) {
  437. throw new TypeError("IdlePixelPlus.addPanel takes the following arguments: (id:string, title:string, content:string|function)");
  438. }
  439. const panels = $("#panels");
  440. panels.append(`
  441. <div id="panel-${id}" style="display: none">
  442. <h1>${title}</h1>
  443. <hr>
  444. <div class="idlepixelplus-panel-content"></div>
  445. </div>
  446. `);
  447. this.panels[id] = {
  448. id: id,
  449. title: title,
  450. content: content
  451. };
  452. this.refreshPanel(id);
  453. }
  454.  
  455. refreshPanel(id) {
  456. if(typeof id !== "string") {
  457. throw new TypeError("IdlePixelPlus.refreshPanel takes the following arguments: (id:string)");
  458. }
  459. const panel = this.panels[id];
  460. if(!panel) {
  461. throw new TypeError(`Error rendering panel with id="${id}" - panel has not be added.`);
  462. }
  463. let content = panel.content;
  464. if(!["string", "function"].includes(typeof content)) {
  465. throw new TypeError(`Error rendering panel with id="${id}" - panel.content must be a string or a function returning a string.`);
  466. }
  467. if(typeof content === "function") {
  468. content = content();
  469. if(typeof content !== "string") {
  470. throw new TypeError(`Error rendering panel with id="${id}" - panel.content must be a string or a function returning a string.`);
  471. }
  472. }
  473. const panelContent = $(`#panel-${id} .idlepixelplus-panel-content`);
  474. panelContent.html(content);
  475. if(id === "idlepixelplus") {
  476. this.forEachPlugin(plugin => {
  477. this.loadPluginConfigs(plugin.id);
  478. });
  479. }
  480. }
  481.  
  482. registerPlugin(plugin) {
  483. if(!(plugin instanceof IdlePixelPlusPlugin)) {
  484. throw new TypeError("IdlePixelPlus.registerPlugin takes the following arguments: (plugin:IdlePixelPlusPlugin)");
  485. }
  486. if(plugin.id in this.plugins) {
  487. throw new Error(`IdlePixelPlusPlugin with id "${plugin.id}" is already registered. Make sure your plugin id is unique!`);
  488. }
  489.  
  490. // TODO: easy config system
  491. // TODO: custom panels
  492.  
  493. this.plugins[plugin.id] = plugin;
  494. this.loadPluginConfigs(plugin.id);
  495. console.log(`IdlePixelPlus registered plugin "${plugin.id}"`);
  496. }
  497.  
  498. forEachPlugin(f) {
  499. if(typeof f !== "function") {
  500. throw new TypeError("IdlePixelPlus.forEachPlugin takes the following arguments: (f:function)");
  501. }
  502. Object.values(this.plugins).forEach(plugin => {
  503. try {
  504. f(plugin);
  505. }
  506. catch(err) {
  507. console.error(`Error occurred while executing function for plugin "${plugin.id}."`);
  508. console.error(err);
  509. }
  510. });
  511. }
  512.  
  513. setPanel(panel) {
  514. if(typeof panel !== "string") {
  515. throw new TypeError("IdlePixelPlus.setPanel takes the following arguments: (panel:string)");
  516. }
  517. window.switch_panels(`panel-${panel}`);
  518. }
  519.  
  520. sendMessage(message) {
  521. if(typeof message !== "string") {
  522. throw new TypeError("IdlePixelPlus.sendMessage takes the following arguments: (message:string)");
  523. }
  524. if(window.websocket && window.websocket.websocket && window.websocket.websocket.readyState==1) {
  525. window.websocket.websocket.send(message);
  526. }
  527. }
  528.  
  529. showToast(title, content) {
  530. show_toast(title, content);
  531. }
  532.  
  533. hideCustomPanels() {
  534. Object.values(this.panels).forEach((panel) => {
  535. const el = $(`#panel-${panel.id}`);
  536. if(el) {
  537. el.css("display", "none");
  538. }
  539. });
  540. }
  541.  
  542. onMessageReceived(data) {
  543. if(this.debug) {
  544. console.log(`IP+ onMessageReceived: ${data}`);
  545. }
  546. if(data) {
  547. this.forEachPlugin((plugin) => {
  548. if(typeof plugin.onMessageReceived === "function") {
  549. plugin.onMessageReceived(data);
  550. }
  551. });
  552. if(data.startsWith("VALID_LOGIN")) {
  553. this.onLogin();
  554. }
  555. else if(data.startsWith("CHAT=")) {
  556. const split = data.substring("CHAT=".length).split("~");
  557. const chatData = {
  558. username: split[0],
  559. tag: null,
  560. sigil: null,
  561. level: split[3],
  562. message: split[4]
  563. };
  564. this.onChat(chatData);
  565. // CHAT=anwinity~none~none~1565~test
  566. // TODO: none and none, probably for tag and sigil
  567. }
  568. }
  569. }
  570.  
  571. onCombatStart() {
  572. if(this.debug) {
  573. console.log(`IP+ onCombatStart`);
  574. }
  575. this.forEachPlugin((plugin) => {
  576. if(typeof plugin.onCombatStart === "function") {
  577. plugin.onCombatStart();
  578. }
  579. });
  580. }
  581.  
  582. onCombatEnd() {
  583. if(this.debug) {
  584. console.log(`IP+ onCombatEnd`);
  585. }
  586. this.forEachPlugin((plugin) => {
  587. if(typeof plugin.onCombatEnd === "function") {
  588. plugin.onCombatEnd();
  589. }
  590. });
  591. }
  592.  
  593. onLogin() {
  594. if(this.debug) {
  595. console.log(`IP+ onLogin`);
  596. }
  597. this.forEachPlugin((plugin) => {
  598. if(typeof plugin.onLogin === "function") {
  599. plugin.onLogin();
  600. }
  601. });
  602. }
  603.  
  604. onVariableSet(key, valueBefore, valueAfter) {
  605. if(this.debug) {
  606. console.log(`IP+ onVariableSet "${key}": "${valueBefore}" -> "${valueAfter}"`);
  607. }
  608. this.forEachPlugin((plugin) => {
  609. if(typeof plugin.onVariableSet === "function") {
  610. plugin.onVariableSet(key, valueBefore, valueAfter);
  611. }
  612. });
  613. if(key == "monster_name") {
  614. const combatBefore = !!(valueBefore && valueBefore!="none");
  615. const combatAfter = !!(valueAfter && valueAfter!="none");
  616. if(!combatBefore && combatAfter) {
  617. this.onCombatStart();
  618. }
  619. else if(combatBefore && !combatAfter) {
  620. this.onCombatEnd();
  621. }
  622. }
  623. }
  624.  
  625. onChat(data) {
  626. if(this.debug) {
  627. console.log(`IP+ onChat`, data);
  628. }
  629. this.forEachPlugin((plugin) => {
  630. if(typeof plugin.onChat === "function") {
  631. plugin.onChat(data);
  632. }
  633. });
  634. }
  635.  
  636. onPanelChanged(panelBefore, panelAfter) {
  637. if(this.debug) {
  638. console.log(`IP+ onPanelChanged "${panelBefore}" -> "${panelAfter}"`);
  639. }
  640. if(panelAfter === "idlepixelplus") {
  641. this.refreshPanel("idlepixelplus");
  642. }
  643. this.forEachPlugin((plugin) => {
  644. if(typeof plugin.onPanelChanged === "function") {
  645. plugin.onPanelChanged(panelBefore, panelAfter);
  646. }
  647. });
  648. }
  649.  
  650. }
  651.  
  652. // Add to window and init
  653. window.IdlePixelPlusPlugin = IdlePixelPlusPlugin;
  654. window.IdlePixelPlus = new IdlePixelPlus();
  655. internal.init.call(window.IdlePixelPlus);
  656.  
  657. })();