IdlePixel+

Idle-Pixel plugin framework

当前为 2022-09-05 提交的版本,查看 最新版本

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

  1. // ==UserScript==
  2. // @name IdlePixel+
  3. // @namespace com.anwinity.idlepixel
  4. // @version 1.2.0
  5. // @description Idle-Pixel plugin framework
  6. // @author Anwinity
  7. // @match *://idle-pixel.com/login/play*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. const VERSION = "1.2.0";
  15.  
  16. if(window.IdlePixelPlus) {
  17. // already loaded
  18. return;
  19. }
  20.  
  21. const LOCAL_STORAGE_KEY_DEBUG = "IdlePixelPlus:debug";
  22.  
  23. const CONFIG_TYPES_LABEL = ["label"];
  24. const CONFIG_TYPES_BOOLEAN = ["boolean", "bool", "checkbox"];
  25. const CONFIG_TYPES_INTEGER = ["integer", "int"];
  26. const CONFIG_TYPES_FLOAT = ["number", "num", "float"];
  27. const CONFIG_TYPES_STRING = ["string", "text"];
  28. const CONFIG_TYPES_SELECT = ["select"];
  29. const CONFIG_TYPES_COLOR = ["color"];
  30.  
  31. const CHAT_COMMAND_NO_OVERRIDE = ["help", "mute", "ban", "pm"];
  32.  
  33. function createCombatZoneObjects() {
  34. const fallback = {
  35. field: {
  36. id: "field",
  37. commonMonsters: [
  38. "Chickens",
  39. "Rats",
  40. "Spiders"
  41. ],
  42. rareMonsters: [
  43. "Lizards",
  44. "Bees"
  45. ],
  46. energyCost: 50,
  47. fightPointCost: 300
  48. },
  49. forest: {
  50. id: "forest",
  51. commonMonsters: [
  52. "Snakes",
  53. "Ants",
  54. "Wolves"
  55. ],
  56. rareMonsters: [
  57. "Ents",
  58. "Thief"
  59. ],
  60. energyCost: 200,
  61. fightPointCost: 600
  62. },
  63. cave: {
  64. id: "cave",
  65. commonMonsters: [
  66. "Bears",
  67. "Goblins",
  68. "Bats"
  69. ],
  70. rareMonsters: [
  71. "Skeletons"
  72. ],
  73. energyCost: 500,
  74. fightPointCost: 900
  75. },
  76. volcano: {
  77. id: "volcano",
  78. commonMonsters: [
  79. "Fire Hawk",
  80. "Fire Snake",
  81. "Fire Golem"
  82. ],
  83. rareMonsters: [
  84. "Fire Witch"
  85. ],
  86. energyCost: 1000,
  87. fightPointCost: 1500
  88. },
  89. northern_field: {
  90. id: "northern_field",
  91. commonMonsters: [
  92. "Ice Hawk",
  93. "Ice Witch",
  94. "Golem"
  95. ],
  96. rareMonsters: [
  97. "Yeti"
  98. ],
  99. energyCost: 3000,
  100. fightPointCost: 2000
  101. }
  102. };
  103. try {
  104. const code = Combat._modal_load_area_data.toString().split(/\r?\n/g);
  105. const zones = {};
  106. let foundSwitch = false;
  107. let endSwitch = false;
  108. let current = null;
  109. code.forEach(line => {
  110. if(endSwitch) {
  111. return;
  112. }
  113. if(!foundSwitch) {
  114. if(line.includes("switch(area)")) {
  115. foundSwitch = true;
  116. }
  117. }
  118. else {
  119. line = line.trim();
  120. if(foundSwitch && !endSwitch && !current && line=='}') {
  121. endSwitch = true;
  122. }
  123. else if(/case /.test(line)) {
  124. // start of zone data
  125. let zoneId = line.replace(/^case\s+"/, "").replace(/":.*$/, "");
  126. current = zones[zoneId] = {id: zoneId};
  127. }
  128. else if(line.startsWith("break;")) {
  129. // end of zone data
  130. current = null;
  131. }
  132. else if(current) {
  133. if(line.startsWith("common_monsters_array")) {
  134. current.commonMonsters = line
  135. .replace("common_monsters_array = [", "")
  136. .replace("];", "")
  137. .split(/\s*,\s*/g)
  138. .map(s => s.substring(1, s.length-1));
  139. }
  140. else if(line.startsWith("rare_monsters_array")) {
  141. current.rareMonsters = line
  142. .replace("rare_monsters_array = [", "")
  143. .replace("];", "")
  144. .split(/\s*,\s*/g)
  145. .map(s => s.substring(1, s.length-1));
  146. }
  147. else if(line.startsWith("energy")) {
  148. current.energyCost = parseInt(line.match(/\d+/)[0]);
  149. }
  150. else if(line.startsWith("fightpoints")) {
  151. current.fightPointCost = parseInt(line.match(/\d+/)[0]);
  152. }
  153. }
  154. }
  155. });
  156. if(!zones || !Object.keys(zones).length) {
  157. console.error("IdlePixelPlus: Could not parse combat zone data, using fallback.");
  158. return fallback;
  159. }
  160. return zones;
  161. }
  162. catch(err) {
  163. console.error("IdlePixelPlus: Could not parse combat zone data, using fallback.", err);
  164. return fallback;
  165. }
  166. }
  167.  
  168. function createOreObjects() {
  169. const ores = {
  170. stone: { smeltable:false, bar: null },
  171. copper: { smeltable:true, bar: "bronze_bar" },
  172. iron: { smeltable:true, bar: "iron_bar" },
  173. silver: { smeltable:true, bar: "silver_bar" },
  174. gold: { smeltable:true, bar: "gold_bar" },
  175. promethium: { smeltable:true, bar: "promethium_bar" }
  176. };
  177. try {
  178. Object.keys(ores).forEach(id => {
  179. const obj = ores[id];
  180. obj.id = id;
  181. obj.oil = Crafting.getOilPerBar(id);
  182. obj.charcoal = Crafting.getCharcoalPerBar(id);
  183. });
  184. }
  185. catch(err) {
  186. console.error("IdlePixelPlus: Could not create ore data. This could adversely affect related functionality.", err);
  187. }
  188. return ores;
  189. }
  190.  
  191. function createSeedObjects() {
  192. // hardcoded for now.
  193. return {
  194. dotted_green_leaf_seeds: {
  195. id: "dotted_green_leaf_seeds",
  196. level: 1,
  197. stopsDying: 15,
  198. time: 15,
  199. bonemealCost: 0
  200. },
  201. stardust_seeds: {
  202. id: "stardust_seeds",
  203. level: 8,
  204. stopsDying: 0,
  205. time: 20,
  206. bonemealCost: 0
  207. },
  208. green_leaf_seeds: {
  209. id: "green_leaf_seeds",
  210. level: 10,
  211. stopsDying: 25,
  212. time: 30,
  213. bonemealCost: 0
  214. },
  215. lime_leaf_seeds: {
  216. id: "lime_leaf_seeds",
  217. level: 25,
  218. stopsDying: 40,
  219. time: 1*60,
  220. bonemealCost: 1
  221. },
  222. gold_leaf_seeds: {
  223. id: "gold_leaf_seeds",
  224. level: 50,
  225. stopsDying: 60,
  226. time: 2*60,
  227. bonemealCost: 10
  228. },
  229. crystal_leaf_seeds: {
  230. id: "crystal_leaf_seeds",
  231. level: 70,
  232. stopsDying: 80,
  233. time: 5*60,
  234. bonemealCost: 25
  235. },
  236. red_mushroom_seeds: {
  237. id: "red_mushroom_seeds",
  238. level: 1,
  239. stopsDying: 0,
  240. time: 5,
  241. bonemealCost: 0
  242. },
  243. tree_seeds: {
  244. id: "tree_seeds",
  245. level: 10,
  246. stopsDying: 25,
  247. time: 5*60,
  248. bonemealCost: 10
  249. },
  250. oak_tree_seeds: {
  251. id: "oak_tree_seeds",
  252. level: 25,
  253. stopsDying: 40,
  254. time: 4*60,
  255. bonemealCost: 25
  256. },
  257. willow_tree_seeds: {
  258. id: "willow_tree_seeds",
  259. level: 37,
  260. stopsDying: 55,
  261. time: 8*60,
  262. bonemealCost: 50
  263. },
  264. maple_tree_seeds: {
  265. id: "maple_tree_seeds",
  266. level: 50,
  267. stopsDying: 65,
  268. time: 12*60,
  269. bonemealCost: 120
  270. },
  271. stardust_tree_seeds: {
  272. id: "stardust_tree_seeds",
  273. level: 65,
  274. stopsDying: 80,
  275. time: 15*60,
  276. bonemealCost: 150
  277. },
  278. pine_tree_seeds: {
  279. id: "pine_tree_seeds",
  280. level: 70,
  281. stopsDying: 85,
  282. time: 17*60,
  283. bonemealCost: 180
  284. }
  285. };
  286. }
  287.  
  288. function createSpellObjects() {
  289. const spells = {};
  290. Object.keys(Magic.spell_info).forEach(id => {
  291. const info = Magic.spell_info[id];
  292. spells[id] = {
  293. id: id,
  294. manaCost: info.mana_cost,
  295. magicBonusRequired: info.magic_bonus
  296. };
  297. });
  298. return spells;
  299. }
  300.  
  301. const INFO = {
  302. ores: createOreObjects(),
  303. seeds: createSeedObjects(),
  304. combatZones: createCombatZoneObjects(),
  305. spells: createSpellObjects()
  306. };
  307.  
  308. function logFancy(s, color="#00f7ff") {
  309. console.log("%cIdlePixelPlus: %c"+s, `color: ${color}; font-weight: bold; font-size: 12pt;`, "color: black; font-weight: normal; font-size: 10pt;");
  310. }
  311.  
  312. class IdlePixelPlusPlugin {
  313.  
  314. constructor(id, opts) {
  315. if(typeof id !== "string") {
  316. throw new TypeError("IdlePixelPlusPlugin constructor takes the following arguments: (id:string, opts?:object)");
  317. }
  318. this.id = id;
  319. this.opts = opts || {};
  320. this.config = null;
  321. }
  322.  
  323. getConfig(name) {
  324. if(!this.config) {
  325. IdlePixelPlus.loadPluginConfigs(this.id);
  326. }
  327. if(this.config) {
  328. return this.config[name];
  329. }
  330. }
  331.  
  332. /*
  333. onConfigsChanged() { }
  334. onLogin() { }
  335. onMessageReceived(data) { }
  336. onVariableSet(key, valueBefore, valueAfter) { }
  337. onChat(data) { }
  338. onPanelChanged(panelBefore, panelAfter) { }
  339. onCombatStart() { }
  340. onCombatEnd() { }
  341. onCustomMessageReceived(player, content, callbackId) { }
  342. onCustomMessagePlayerOffline(player, content) { }
  343. */
  344.  
  345. }
  346.  
  347. const internal = {
  348. init() {
  349. const self = this;
  350.  
  351. $("head").append(`
  352. <style>
  353. .ipp-chat-command-help {
  354. padding: 0.5em 0;
  355. }
  356. .ipp-chat-command-help:first-child {
  357. padding-top: 0;
  358. }
  359. .ipp-chat-command-help:last-child {
  360. padding-bottom: 0;
  361. }
  362. dialog.ipp-dialog {
  363. background-color: white;
  364. border: 1px solid rgba(0, 0, 0, 0.2);
  365. width: 500px;
  366. max-width: 800px;
  367. border-radius: 5px;
  368. display: flex;
  369. flex-direction: column;
  370. justify-content: flex-start;
  371. }
  372. dialog.ipp-dialog > div {
  373. width: 100%;
  374. }
  375. dialog.ipp-dialog > .ipp-dialog-header > h4 {
  376. margin-bottom: 0;
  377. }
  378. dialog.ipp-dialog > .ipp-dialog-header {
  379. border-bottom: 1px solid rgba(0, 0, 0, 0.2);
  380. padding-bottom: 0.25em;
  381. }
  382. dialog.ipp-dialog > .ipp-dialog-actions {
  383. padding-top: 0.25em;
  384. padding-bottom: 0.25em;
  385. }
  386. dialog.ipp-dialog > .ipp-dialog-actions {
  387. border-top: 1px solid rgba(0, 0, 0, 0.2);
  388. padding-top: 0.25em;
  389. text-align: right;
  390. }
  391. dialog.ipp-dialog > .ipp-dialog-actions > button {
  392. margin: 4px;
  393. }
  394. </style>
  395. `);
  396.  
  397. // hook into websocket messages
  398. const hookIntoOnMessage = () => {
  399. try {
  400. const original_onmessage = window.websocket.connected_socket.onmessage;
  401. if(typeof original_onmessage === "function") {
  402. window.websocket.connected_socket.onmessage = function(event) {
  403. original_onmessage.apply(window.websocket.connected_socket, arguments);
  404. self.onMessageReceived(event.data);
  405. }
  406. return true;
  407. }
  408. else {
  409. return false;
  410. }
  411. }
  412. catch(err) {
  413. console.error("Had trouble hooking into websocket...");
  414. return false;
  415. }
  416. };
  417. $(function() {
  418. if(!hookIntoOnMessage()) {
  419. // try once more
  420. setTimeout(hookIntoOnMessage, 40);
  421. }
  422. });
  423.  
  424. // hook into Chat.send
  425. const original_chat_send = Chat.send;
  426. Chat.send = function() {
  427. const input = $("#chat-area-input");
  428. let message = input.val();
  429. if(message.length == 0) {
  430. return;
  431. }
  432. if(message.startsWith("/")) {
  433. const space = message.indexOf(" ");
  434. let command;
  435. let data;
  436. if(space <= 0) {
  437. command = message.substring(1);
  438. data = "";
  439. }
  440. else {
  441. command = message.substring(1, space);
  442. data = message.substring(space+1);
  443. }
  444. if(window.IdlePixelPlus.handleCustomChatCommand(command, data)) {
  445. input.val("");
  446. }
  447. else {
  448. original_chat_send();
  449. }
  450. }
  451. else {
  452. original_chat_send();
  453. }
  454. };
  455.  
  456. // hook into Items.set, which is where var_ values are set
  457. const original_items_set = Items.set;
  458. Items.set = function(key, value) {
  459. let valueBefore = window["var_"+key];
  460. original_items_set.apply(this, arguments);
  461. let valueAfter = window["var_"+key];
  462. self.onVariableSet(key, valueBefore, valueAfter);
  463. }
  464.  
  465. // hook into switch_panels, which is called when the main panel is changed. This is also used for custom panels.
  466. const original_switch_panels = window.switch_panels;
  467. window.switch_panels = function(id) {
  468. let panelBefore = Globals.currentPanel;
  469. if(panelBefore && panelBefore.startsWith("panel-")) {
  470. panelBefore = panelBefore.substring("panel-".length);
  471. }
  472. self.hideCustomPanels();
  473. original_switch_panels.apply(this, arguments);
  474. let panelAfter = Globals.currentPanel;
  475. if(panelAfter && panelAfter.startsWith("panel-")) {
  476. panelAfter = panelAfter.substring("panel-".length);
  477. }
  478. self.onPanelChanged(panelBefore, panelAfter);
  479. }
  480.  
  481. // create plugin menu item and panel
  482. const lastMenuItem = $("#menu-bar-buttons > .hover-menu-bar-item").last();
  483. lastMenuItem.after(`
  484. <div onclick="IdlePixelPlus.setPanel('idlepixelplus')" class="hover hover-menu-bar-item">
  485. <img id="menu-bar-idlepixelplus-icon" src="https://anwinity.com/idlepixelplus/plugins.png"> PLUGINS
  486. </div>
  487. `);
  488. self.addPanel("idlepixelplus", "IdlePixel+ Plugins", function() {
  489. let content = `
  490. <style>
  491. .idlepixelplus-plugin-box {
  492. display: block;
  493. position: relative;
  494. padding: 0.25em;
  495. color: white;
  496. background-color: rgb(107, 107, 107);
  497. border: 1px solid black;
  498. border-radius: 6px;
  499. margin-bottom: 0.5em;
  500. }
  501. .idlepixelplus-plugin-box .idlepixelplus-plugin-settings-button {
  502. position: absolute;
  503. right: 2px;
  504. top: 2px;
  505. cursor: pointer;
  506. }
  507. .idlepixelplus-plugin-box .idlepixelplus-plugin-config-section {
  508. display: grid;
  509. grid-template-columns: minmax(100px, min-content) 1fr;
  510. row-gap: 0.5em;
  511. column-gap: 0.5em;
  512. white-space: nowrap;
  513. }
  514. </style>
  515. `;
  516. self.forEachPlugin(plugin => {
  517. let id = plugin.id;
  518. let name = "An IdlePixel+ Plugin!";
  519. let description = "";
  520. let author = "unknown";
  521. if(plugin.opts.about) {
  522. let about = plugin.opts.about;
  523. name = about.name || name;
  524. description = about.description || description;
  525. author = about.author || author;
  526. }
  527. content += `
  528. <div id="idlepixelplus-plugin-box-${id}" class="idlepixelplus-plugin-box">
  529. <strong><u>${name||id}</u></strong> (by ${author})<br />
  530. <span>${description}</span><br />
  531. <div class="idlepixelplus-plugin-config-section" style="display: none">
  532. <hr style="grid-column: span 2">
  533. `;
  534. if(plugin.opts.config && Array.isArray(plugin.opts.config)) {
  535. plugin.opts.config.forEach(cfg => {
  536. if(CONFIG_TYPES_LABEL.includes(cfg.type)) {
  537. content += `<h5 style="grid-column: span 2; margin-bottom: 0; font-weight: 600">${cfg.label}</h5>`;
  538. }
  539. else if(CONFIG_TYPES_BOOLEAN.includes(cfg.type)) {
  540. content += `
  541. <div>
  542. <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
  543. </div>
  544. <div>
  545. <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="checkbox" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" />
  546. </div>
  547. `;
  548. }
  549. else if(CONFIG_TYPES_INTEGER.includes(cfg.type)) {
  550. content += `
  551. <div>
  552. <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
  553. </div>
  554. <div>
  555. <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="number" step="1" min="${cfg.min || ''}" max="${cfg.max || ''}" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" />
  556. </div>
  557. `;
  558. }
  559. else if(CONFIG_TYPES_FLOAT.includes(cfg.type)) {
  560. content += `
  561. <div>
  562. <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
  563. </div>
  564. <div>
  565. <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="number" step="${cfg.step || ''}" min="${cfg.min || ''}" max="${cfg.max || ''}" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" />
  566. </div>
  567. `;
  568. }
  569. else if(CONFIG_TYPES_STRING.includes(cfg.type)) {
  570. content += `
  571. <div>
  572. <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
  573. </div>
  574. <div>
  575. <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="text" maxlength="${cfg.max || ''}" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" />
  576. </div>
  577. `;
  578. }
  579. else if(CONFIG_TYPES_COLOR.includes(cfg.type)) {
  580. content += `
  581. <div>
  582. <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
  583. </div>
  584. <div>
  585. <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="color" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" />
  586. </div>
  587. `;
  588. }
  589. else if(CONFIG_TYPES_SELECT.includes(cfg.type)) {
  590. content += `
  591. <div>
  592. <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
  593. </div>
  594. <div>
  595. <select id="idlepixelplus-config-${plugin.id}-${cfg.id}" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)">
  596. `;
  597. if(cfg.options && Array.isArray(cfg.options)) {
  598. cfg.options.forEach(option => {
  599. if(typeof option === "string") {
  600. content += `<option value="${option}">${option}</option>`;
  601. }
  602. else {
  603. content += `<option value="${option.value}">${option.label || option.value}</option>`;
  604. }
  605. });
  606. }
  607. content += `
  608. </select>
  609. </div>
  610. `;
  611. }
  612. });
  613. content += `
  614. <div style="grid-column: span 2">
  615. <button id="idlepixelplus-configbutton-${plugin.id}-reload" onclick="IdlePixelPlus.loadPluginConfigs('${id}')">Reload</button>
  616. <button id="idlepixelplus-configbutton-${plugin.id}-apply" onclick="IdlePixelPlus.savePluginConfigs('${id}')">Apply</button>
  617. </div>
  618. `;
  619. }
  620. content += "</div>";
  621. if(plugin.opts.config) {
  622. content += `
  623. <div class="idlepixelplus-plugin-settings-button">
  624. <button onclick="$('#idlepixelplus-plugin-box-${id} .idlepixelplus-plugin-config-section').toggle()">Settings</button>
  625. </div>`;
  626. }
  627. content += "</div>";
  628. });
  629.  
  630. return content;
  631. });
  632.  
  633. $("#chat-area-input").attr("autocomplete", "off");
  634.  
  635. logFancy(`(v${self.version}) initialized.`);
  636. }
  637. };
  638.  
  639. class IdlePixelPlus {
  640.  
  641. constructor() {
  642. this.version = VERSION;
  643. this.plugins = {};
  644. this.panels = {};
  645. this.debug = false;
  646. this.info = INFO;
  647. this.nextUniqueId = 1;
  648. this.customMessageCallbacks = {};
  649. this.customChatCommands = {
  650. help: (command, data) => {
  651. console.log("help", command, data);
  652. }
  653. };
  654. this.customChatHelp = {};
  655. this.customDialogOptions = {};
  656.  
  657. if(localStorage.getItem(LOCAL_STORAGE_KEY_DEBUG) == "1") {
  658. this.debug = true;
  659. }
  660. }
  661.  
  662. getCustomDialogData(id) {
  663. const el = document.querySelector(`dialog#${id}.ipp-dialog`);
  664. if(el) {
  665. const result = {};
  666. $(el).find("[data-key]").each(function() {
  667. const dataElement = $(this);
  668. const dataKey = dataElement.attr("data-key");
  669. if(["INPUT", "SELECT", "TEXTAREA"].includes(dataElement.prop("tagName"))) {
  670. result[dataKey] = dataElement.val();
  671. }
  672. else {
  673. result[dataKey] = dataElement.text();
  674. }
  675. });
  676. return result;
  677. }
  678. }
  679.  
  680. openCustomDialog(id, noEvent=false) {
  681. this.closeCustomDialog(id, true);
  682. const el = document.querySelector(`dialog#${id}.ipp-dialog`);
  683. if(el) {
  684. el.style.display = "";
  685. el.showModal();
  686. const opts = this.customDialogOptions[id];
  687. if(!noEvent && opts && typeof opts.onOpen === "function") {
  688. opts.onOpen(opts);
  689. }
  690. }
  691. }
  692.  
  693. closeCustomDialog(id, noEvent=false) {
  694. const el = document.querySelector(`dialog#${id}.ipp-dialog`);
  695. if(el) {
  696. el.close();
  697. el.style.display = "none";
  698. const opts = this.customDialogOptions[id];
  699. if(!noEvent && opts && typeof opts.onClose === "function") {
  700. opts.onClose(opts);
  701. }
  702. }
  703. }
  704.  
  705. destroyCustomDialog(id, noEvent=false) {
  706. const el = document.querySelector(`dialog#${id}.ipp-dialog`);
  707. if(el) {
  708. el.remove();
  709. const opts = this.customDialogOptions[id];
  710. if(!noEvent && opts && typeof opts.onDestroy === "function") {
  711. opts.onDestroy(opts);
  712. }
  713. }
  714. delete this.customDialogOptions[id];
  715. }
  716.  
  717. createCustomDialog(id, opts={}) {
  718. const self = this;
  719. this.destroyCustomDialog(id);
  720. this.customDialogOptions[id] = opts;
  721. const el = $("body").append(`
  722. <dialog id="${id}" class="ipp-dialog" style="display: none">
  723. <div class="ipp-dialog-header">
  724. <h4>${opts.title||''}</h4>
  725. </div>
  726. <div class="ipp-dialog-content"></div>
  727. <div class="ipp-dialog-actions"></div>
  728. </dialog>
  729. `);
  730. const headerElement = el.find(".ipp-dialog-header");
  731. const contentElement = el.find(".ipp-dialog-content");
  732. const actionsElement = el.find(".ipp-dialog-actions");
  733.  
  734. if(!opts.title) {
  735. headerElement.hide();
  736. }
  737.  
  738. if(typeof opts.content === "string") {
  739. contentElement.append(opts.content);
  740. }
  741.  
  742. let actions = opts.actions;
  743. if(actions) {
  744. if(!Array.isArray(actions)) {
  745. actions = [actions];
  746. }
  747. actions.forEach(action => {
  748. let label;
  749. let primary = false;
  750. if(typeof action === "string") {
  751. label = action;
  752. }
  753. else {
  754. label = action.label || action.action;
  755. primary = action.primary===true;
  756. action = action.action;
  757. }
  758. actionsElement.append(`<button data-action="${action}" class="${primary?'background-primary':''}">${label}</button>`);
  759. });
  760. actionsElement.find("button").on("click", function(e) {
  761. if(typeof opts.onAction === "function") {
  762. e.stopPropagation();
  763. const button = $(this);
  764. const buttonAction = button.attr("data-action");
  765. const data = self.getCustomDialogData(id);
  766. const actionReturn = opts.onAction(buttonAction, data);
  767. if(actionReturn) {
  768. self.closeCustomDialog(id);
  769. }
  770. }
  771. });
  772. }
  773. else {
  774. el.find(".ipp-dialog-actions").hide();
  775. }
  776.  
  777. el.click(function(e) {
  778. const rect = e.target.getBoundingClientRect();
  779. const inside =
  780. rect.top <= e.clientY &&
  781. rect.left <= e.clientX &&
  782. e.clientX <= rect.left + rect.width &&
  783. e.clientY <= rect.top + rect.height;
  784. if(!inside) {
  785. self.closeCustomDialog(id);
  786. e.stopPropagation();
  787. }
  788. });
  789.  
  790. if(typeof opts.onCreate === "function") {
  791. opts.onCreate();
  792. }
  793. if(opts.openImmediately === true) {
  794. this.openCustomDialog(id);
  795. }
  796. }
  797.  
  798.  
  799. registerCustomChatCommand(command, f, help) {
  800. if(Array.isArray(command)) {
  801. command.forEach(cmd => this.registerCustomChatCommand(cmd, f, help));
  802. return;
  803. }
  804. if(typeof command !== "string" || typeof f !== "function") {
  805. throw new TypeError("IdlePixelPlus.registerCustomChatCommand takes the following arguments: (command:string, f:function)");
  806. }
  807. if(CHAT_COMMAND_NO_OVERRIDE.includes(command)) {
  808. throw new Error(`Cannot override the following chat commands: ${CHAT_COMMAND_NO_OVERRIDE.join(", ")}`);
  809. }
  810. if(command in this.customChatCommands) {
  811. console.warn(`IdlePixelPlus: re-registering custom chat command "${command}" which already exists.`);
  812. }
  813. this.customChatCommands[command] = f;
  814. if(help && typeof help === "string") {
  815. this.customChatHelp[command] = help.replace(/%COMMAND%/g, command);
  816. }
  817. else {
  818. delete this.customChatHelp[command];
  819. }
  820. }
  821.  
  822. handleCustomChatCommand(command, message) {
  823. // return true if command handler exists, false otherwise
  824. const f = this.customChatCommands[command];
  825. if(typeof f === "function") {
  826. try {
  827. f(command, message);
  828. }
  829. catch(err) {
  830. console.error(`Error executing custom command "${command}"`, err);
  831. }
  832. return true;
  833. }
  834. return false;
  835. }
  836.  
  837. uniqueId() {
  838. return this.nextUniqueId++;
  839. }
  840.  
  841. setDebug(debug) {
  842. if(debug) {
  843. this.debug = true;
  844. localStorage.setItem(LOCAL_STORAGE_KEY_DEBUG, "1");
  845. }
  846. else {
  847. this.debug = false;
  848. localStorage.removeItem(LOCAL_STORAGE_KEY_DEBUG);
  849. }
  850. }
  851.  
  852. getVar(name, type) {
  853. let s = window[`var_${name}`];
  854. if(type) {
  855. switch(type) {
  856. case "int":
  857. case "integer":
  858. return parseInt(s);
  859. case "number":
  860. case "float":
  861. return parseFloat(s);
  862. case "boolean":
  863. case "bool":
  864. if(s=="true") return true;
  865. if(s=="false") return false;
  866. return undefined;
  867. }
  868. }
  869. return s;
  870. }
  871.  
  872. getVarOrDefault(name, defaultValue, type) {
  873. let s = window[`var_${name}`];
  874. if(s==null || typeof s === "undefined") {
  875. return defaultValue;
  876. }
  877. if(type) {
  878. let value;
  879. switch(type) {
  880. case "int":
  881. case "integer":
  882. value = parseInt(s);
  883. return isNaN(value) ? defaultValue : value;
  884. case "number":
  885. case "float":
  886. value = parseFloat(s);
  887. return isNaN(value) ? defaultValue : value;
  888. case "boolean":
  889. case "bool":
  890. if(s=="true") return true;
  891. if(s=="false") return false;
  892. return defaultValue;
  893. }
  894. }
  895. return s;
  896. }
  897.  
  898. setPluginConfigUIDirty(id, dirty) {
  899. if(typeof id !== "string" || typeof dirty !== "boolean") {
  900. throw new TypeError("IdlePixelPlus.setPluginConfigUIDirty takes the following arguments: (id:string, dirty:boolean)");
  901. }
  902. const plugin = this.plugins[id];
  903. const button = $(`#idlepixelplus-configbutton-${plugin.id}-apply`);
  904. if(button) {
  905. button.prop("disabled", !(dirty));
  906. }
  907. }
  908.  
  909. loadPluginConfigs(id) {
  910. if(typeof id !== "string") {
  911. throw new TypeError("IdlePixelPlus.reloadPluginConfigs takes the following arguments: (id:string)");
  912. }
  913. const plugin = this.plugins[id];
  914. const config = {};
  915. let stored;
  916. try {
  917. stored = JSON.parse(localStorage.getItem(`idlepixelplus.${id}.config`) || "{}");
  918. }
  919. catch(err) {
  920. console.error(`Failed to load configs for plugin with id "${id} - will use defaults instead."`);
  921. stored = {};
  922. }
  923. if(plugin.opts.config && Array.isArray(plugin.opts.config)) {
  924. plugin.opts.config.forEach(cfg => {
  925. const el = $(`#idlepixelplus-config-${plugin.id}-${cfg.id}`);
  926. let value = stored[cfg.id];
  927. if(value==null || typeof value === "undefined") {
  928. value = cfg.default;
  929. }
  930. config[cfg.id] = value;
  931.  
  932. if(el) {
  933. if(CONFIG_TYPES_BOOLEAN.includes(cfg.type) && typeof value === "boolean") {
  934. el.prop("checked", value);
  935. }
  936. else if(CONFIG_TYPES_INTEGER.includes(cfg.type) && typeof value === "number") {
  937. el.val(value);
  938. }
  939. else if(CONFIG_TYPES_FLOAT.includes(cfg.type) && typeof value === "number") {
  940. el.val(value);
  941. }
  942. else if(CONFIG_TYPES_STRING.includes(cfg.type) && typeof value === "string") {
  943. el.val(value);
  944. }
  945. else if(CONFIG_TYPES_SELECT.includes(cfg.type) && typeof value === "string") {
  946. el.val(value);
  947. }
  948. else if(CONFIG_TYPES_COLOR.includes(cfg.type) && typeof value === "string") {
  949. el.val(value);
  950. }
  951. }
  952. });
  953. }
  954. plugin.config = config;
  955. this.setPluginConfigUIDirty(id, false);
  956. if(typeof plugin.onConfigsChanged === "function") {
  957. plugin.onConfigsChanged();
  958. }
  959. }
  960.  
  961. savePluginConfigs(id) {
  962. if(typeof id !== "string") {
  963. throw new TypeError("IdlePixelPlus.savePluginConfigs takes the following arguments: (id:string)");
  964. }
  965. const plugin = this.plugins[id];
  966. const config = {};
  967. if(plugin.opts.config && Array.isArray(plugin.opts.config)) {
  968. plugin.opts.config.forEach(cfg => {
  969. const el = $(`#idlepixelplus-config-${plugin.id}-${cfg.id}`);
  970. let value;
  971. if(CONFIG_TYPES_BOOLEAN.includes(cfg.type)) {
  972. config[cfg.id] = el.is(":checked");
  973. }
  974. else if(CONFIG_TYPES_INTEGER.includes(cfg.type)) {
  975. config[cfg.id] = parseInt(el.val());
  976. }
  977. else if(CONFIG_TYPES_FLOAT.includes(cfg.type)) {
  978. config[cfg.id] = parseFloat(el.val());
  979. }
  980. else if(CONFIG_TYPES_STRING.includes(cfg.type)) {
  981. config[cfg.id] = el.val();
  982. }
  983. else if(CONFIG_TYPES_SELECT.includes(cfg.type)) {
  984. config[cfg.id] = el.val();
  985. }
  986. else if(CONFIG_TYPES_COLOR.includes(cfg.type)) {
  987. config[cfg.id] = el.val();
  988. }
  989. });
  990. }
  991. plugin.config = config;
  992. localStorage.setItem(`idlepixelplus.${id}.config`, JSON.stringify(config));
  993. this.setPluginConfigUIDirty(id, false);
  994. if(typeof plugin.onConfigsChanged === "function") {
  995. plugin.onConfigsChanged();
  996. }
  997. }
  998.  
  999. addPanel(id, title, content) {
  1000. if(typeof id !== "string" || typeof title !== "string" || (typeof content !== "string" && typeof content !== "function") ) {
  1001. throw new TypeError("IdlePixelPlus.addPanel takes the following arguments: (id:string, title:string, content:string|function)");
  1002. }
  1003. const panels = $("#panels");
  1004. panels.append(`
  1005. <div id="panel-${id}" style="display: none">
  1006. <h1>${title}</h1>
  1007. <hr>
  1008. <div class="idlepixelplus-panel-content"></div>
  1009. </div>
  1010. `);
  1011. this.panels[id] = {
  1012. id: id,
  1013. title: title,
  1014. content: content
  1015. };
  1016. this.refreshPanel(id);
  1017. }
  1018.  
  1019. refreshPanel(id) {
  1020. if(typeof id !== "string") {
  1021. throw new TypeError("IdlePixelPlus.refreshPanel takes the following arguments: (id:string)");
  1022. }
  1023. const panel = this.panels[id];
  1024. if(!panel) {
  1025. throw new TypeError(`Error rendering panel with id="${id}" - panel has not be added.`);
  1026. }
  1027. let content = panel.content;
  1028. if(!["string", "function"].includes(typeof content)) {
  1029. throw new TypeError(`Error rendering panel with id="${id}" - panel.content must be a string or a function returning a string.`);
  1030. }
  1031. if(typeof content === "function") {
  1032. content = content();
  1033. if(typeof content !== "string") {
  1034. throw new TypeError(`Error rendering panel with id="${id}" - panel.content must be a string or a function returning a string.`);
  1035. }
  1036. }
  1037. const panelContent = $(`#panel-${id} .idlepixelplus-panel-content`);
  1038. panelContent.html(content);
  1039. if(id === "idlepixelplus") {
  1040. this.forEachPlugin(plugin => {
  1041. this.loadPluginConfigs(plugin.id);
  1042. });
  1043. }
  1044. }
  1045.  
  1046. registerPlugin(plugin) {
  1047. if(!(plugin instanceof IdlePixelPlusPlugin)) {
  1048. throw new TypeError("IdlePixelPlus.registerPlugin takes the following arguments: (plugin:IdlePixelPlusPlugin)");
  1049. }
  1050. if(plugin.id in this.plugins) {
  1051. throw new Error(`IdlePixelPlusPlugin with id "${plugin.id}" is already registered. Make sure your plugin id is unique!`);
  1052. }
  1053.  
  1054. this.plugins[plugin.id] = plugin;
  1055. this.loadPluginConfigs(plugin.id);
  1056. let versionString = plugin.opts&&plugin.opts.about&&plugin.opts.about.version ? ` (v${plugin.opts.about.version})` : "";
  1057. logFancy(`registered plugin "${plugin.id}"${versionString}`);
  1058. }
  1059.  
  1060. forEachPlugin(f) {
  1061. if(typeof f !== "function") {
  1062. throw new TypeError("IdlePixelPlus.forEachPlugin takes the following arguments: (f:function)");
  1063. }
  1064. Object.values(this.plugins).forEach(plugin => {
  1065. try {
  1066. f(plugin);
  1067. }
  1068. catch(err) {
  1069. console.error(`Error occurred while executing function for plugin "${plugin.id}."`);
  1070. console.error(err);
  1071. }
  1072. });
  1073. }
  1074.  
  1075. setPanel(panel) {
  1076. if(typeof panel !== "string") {
  1077. throw new TypeError("IdlePixelPlus.setPanel takes the following arguments: (panel:string)");
  1078. }
  1079. window.switch_panels(`panel-${panel}`);
  1080. }
  1081.  
  1082. sendMessage(message) {
  1083. if(typeof message !== "string") {
  1084. throw new TypeError("IdlePixelPlus.sendMessage takes the following arguments: (message:string)");
  1085. }
  1086. if(window.websocket && window.websocket.connected_socket && window.websocket.connected_socket.readyState==1) {
  1087. window.websocket.connected_socket.send(message);
  1088. }
  1089. }
  1090.  
  1091. showToast(title, content) {
  1092. show_toast(title, content);
  1093. }
  1094.  
  1095. hideCustomPanels() {
  1096. Object.values(this.panels).forEach((panel) => {
  1097. const el = $(`#panel-${panel.id}`);
  1098. if(el) {
  1099. el.css("display", "none");
  1100. }
  1101. });
  1102. }
  1103.  
  1104. onMessageReceived(data) {
  1105. if(this.debug) {
  1106. console.log(`IP+ onMessageReceived: ${data}`);
  1107. }
  1108. if(data) {
  1109. this.forEachPlugin((plugin) => {
  1110. if(typeof plugin.onMessageReceived === "function") {
  1111. plugin.onMessageReceived(data);
  1112. }
  1113. });
  1114. if(data.startsWith("VALID_LOGIN")) {
  1115. this.onLogin();
  1116. }
  1117. else if(data.startsWith("CHAT=")) {
  1118. const split = data.substring("CHAT=".length).split("~");
  1119. const chatData = {
  1120. username: split[0],
  1121. sigil: split[1],
  1122. tag: split[2],
  1123. level: parseInt(split[3]),
  1124. message: split[4]
  1125. };
  1126. this.onChat(chatData);
  1127. // CHAT=anwinity~none~none~1565~test
  1128. }
  1129. else if(data.startsWith("CUSTOM=")) {
  1130. const customData = data.substring("CUSTOM=".length);
  1131. const tilde = customData.indexOf("~");
  1132. if(tilde > 0) {
  1133. const fromPlayer = customData.substring(0, tilde);
  1134. const content = customData.substring(tilde+1);
  1135. this.onCustomMessageReceived(fromPlayer, content);
  1136. }
  1137. }
  1138. }
  1139. }
  1140.  
  1141. deleteCustomMessageCallback(callbackId) {
  1142. if(this.debug) {
  1143. console.log(`IP+ deleteCustomMessageCallback`, callbackId);
  1144. }
  1145. delete this.customMessageCallbacks[callbackId];
  1146. }
  1147.  
  1148. requestPluginManifest(player, callback, pluginId) {
  1149. if(typeof pluginId === "string") {
  1150. pluginId = [pluginId];
  1151. }
  1152. if(Array.isArray(pluginId)) {
  1153. pluginId = JSON.stringify(pluginId);
  1154. }
  1155. this.sendCustomMessage(player, {
  1156. content: "PLUGIN_MANIFEST" + (pluginId ? `:${pluginId}` : ''),
  1157. onResponse: function(respPlayer, content) {
  1158. if(typeof callback === "function") {
  1159. callback(respPlayer, JSON.parse(content));
  1160. }
  1161. else {
  1162. console.log(`Plugin Manifest: ${respPlayer}`, content);
  1163. }
  1164. },
  1165. onOffline: function(respPlayer, content) {
  1166. if(typeof callback === "function") {
  1167. callback(respPlayer, false);
  1168. }
  1169. },
  1170. timeout: 10000
  1171. });
  1172. }
  1173.  
  1174. sendCustomMessage(toPlayer, opts) {
  1175. if(this.debug) {
  1176. console.log(`IP+ sendCustomMessage`, toPlayer, opts);
  1177. }
  1178. const reply = !!(opts.callbackId);
  1179. const content = typeof opts.content === "string" ? opts.content : JSON.stringify(opts.content);
  1180. const callbackId = reply ? opts.callbackId : this.uniqueId();
  1181. const responseHandler = typeof opts.onResponse === "function" ? opts.onResponse : null;
  1182. const offlineHandler = opts.onOffline===true ? () => { this.deleteCustomMessageCallback(callbackId); } : (typeof opts.onOffline === "function" ? opts.onOffline : null);
  1183. const timeout = typeof opts.timeout === "number" ? opts.timeout : -1;
  1184.  
  1185. if(responseHandler || offlineHandler) {
  1186. const handler = {
  1187. id: callbackId,
  1188. player: toPlayer,
  1189. responseHandler: responseHandler,
  1190. offlineHandler: offlineHandler,
  1191. timeout: typeof timeout === "number" ? timeout : -1,
  1192. };
  1193. if(callbackId) {
  1194. this.customMessageCallbacks[callbackId] = handler;
  1195. if(handler.timeout > 0) {
  1196. setTimeout(() => {
  1197. this.deleteCustomMessageCallback(callbackId);
  1198. }, handler.timeout);
  1199. }
  1200. }
  1201. }
  1202. const message = `CUSTOM=${toPlayer}~IPP${reply?'R':''}${callbackId}:${content}`;
  1203. if(message.length > 255) {
  1204. console.warn("The resulting websocket message from IdlePixelPlus.sendCustomMessage has a length limit of 255 characters. Recipients may not receive the full message!");
  1205. }
  1206. this.sendMessage(message);
  1207. }
  1208.  
  1209. onCustomMessageReceived(fromPlayer, content) {
  1210. if(this.debug) {
  1211. console.log(`IP+ onCustomMessageReceived`, fromPlayer, content);
  1212. }
  1213. const offline = content == "PLAYER_OFFLINE";
  1214. let callbackId = null;
  1215. let originalCallbackId = null;
  1216. let reply = false;
  1217. const ippMatcher = content.match(/^IPP(\w+):/);
  1218. if(ippMatcher) {
  1219. originalCallbackId = callbackId = ippMatcher[1];
  1220. let colon = content.indexOf(":");
  1221. content = content.substring(colon+1);
  1222. if(callbackId.startsWith("R")) {
  1223. callbackId = callbackId.substring(1);
  1224. reply = true;
  1225. }
  1226. }
  1227.  
  1228. // special built-in messages
  1229. if(content.startsWith("PLUGIN_MANIFEST")) {
  1230. const manifest = {};
  1231. let filterPluginIds = null;
  1232. if(content.includes(":")) {
  1233. content = content.substring("PLUGIN_MANIFEST:".length);
  1234. filterPluginIds = JSON.parse(content).map(s => s.replace("~", ""));
  1235. }
  1236. this.forEachPlugin(plugin => {
  1237. let id = plugin.id.replace("~", "");
  1238. if(filterPluginIds && !filterPluginIds.includes(id)) {
  1239. return;
  1240. }
  1241. let version = "unknown";
  1242. if(plugin.opts && plugin.opts.about && plugin.opts.about.version) {
  1243. version = plugin.opts.about.version.replace("~", "");
  1244. }
  1245. manifest[id] = version;
  1246. });
  1247. manifest.IdlePixelPlus = IdlePixelPlus.version;
  1248. this.sendCustomMessage(fromPlayer, {
  1249. content: manifest,
  1250. callbackId: callbackId
  1251. });
  1252. return;
  1253. }
  1254.  
  1255. const callbacks = this.customMessageCallbacks;
  1256. if(reply) {
  1257. const handler = callbacks[callbackId];
  1258. if(handler && typeof handler.responseHandler === "function") {
  1259. try {
  1260. if(handler.responseHandler(fromPlayer, content, originalCallbackId)) {
  1261. this.deleteCustomMessageCallback(callbackId);
  1262. }
  1263. }
  1264. catch(err) {
  1265. console.error("Error executing custom message response handler.", {player: fromPlayer, content: content, handler: handler});
  1266. }
  1267. }
  1268. }
  1269. else if(offline) {
  1270. Object.values(callbacks).forEach(handler => {
  1271. try {
  1272. if(handler.player.toLowerCase()==fromPlayer.toLowerCase() && typeof handler.offlineHandler === "function" && handler.offlineHandler(fromPlayer, content)) {
  1273. this.deleteCustomMessageCallback(handler.id);
  1274. }
  1275. }
  1276. catch(err) {
  1277. console.error("Error executing custom message offline handler.", {player: fromPlayer, content: content, handler: handler});
  1278. }
  1279. });
  1280. }
  1281.  
  1282. if(offline) {
  1283. this.onCustomMessagePlayerOffline(fromPlayer, content);
  1284. }
  1285. else {
  1286. this.forEachPlugin((plugin) => {
  1287. if(typeof plugin.onCustomMessageReceived === "function") {
  1288. plugin.onCustomMessageReceived(fromPlayer, content, originalCallbackId);
  1289. }
  1290. });
  1291. }
  1292. }
  1293.  
  1294. onCustomMessagePlayerOffline(fromPlayer, content) {
  1295. if(this.debug) {
  1296. console.log(`IP+ onCustomMessagePlayerOffline`, fromPlayer, content);
  1297. }
  1298. this.forEachPlugin((plugin) => {
  1299. if(typeof plugin.onCustomMessagePlayerOffline === "function") {
  1300. plugin.onCustomMessagePlayerOffline(fromPlayer, content);
  1301. }
  1302. });
  1303. }
  1304.  
  1305. onCombatStart() {
  1306. if(this.debug) {
  1307. console.log(`IP+ onCombatStart`);
  1308. }
  1309. this.forEachPlugin((plugin) => {
  1310. if(typeof plugin.onCombatStart === "function") {
  1311. plugin.onCombatStart();
  1312. }
  1313. });
  1314. }
  1315.  
  1316. onCombatEnd() {
  1317. if(this.debug) {
  1318. console.log(`IP+ onCombatEnd`);
  1319. }
  1320. this.forEachPlugin((plugin) => {
  1321. if(typeof plugin.onCombatEnd === "function") {
  1322. plugin.onCombatEnd();
  1323. }
  1324. });
  1325. }
  1326.  
  1327. onLogin() {
  1328. if(this.debug) {
  1329. console.log(`IP+ onLogin`);
  1330. }
  1331. logFancy("login detected");
  1332. this.forEachPlugin((plugin) => {
  1333. if(typeof plugin.onLogin === "function") {
  1334. plugin.onLogin();
  1335. }
  1336. });
  1337. $("#chat-area").append(`
  1338. <div class="ipp-chat-command-help">
  1339. <span><strong>FYI: </strong> Use the /help command to see information on available chat commands.</span>
  1340. </div>
  1341. `);
  1342. if(Chat._auto_scroll) {
  1343. $("#chat-area").scrollTop($("#chat-area")[0].scrollHeight);
  1344. }
  1345.  
  1346. }
  1347.  
  1348. onVariableSet(key, valueBefore, valueAfter) {
  1349. if(this.debug) {
  1350. console.log(`IP+ onVariableSet "${key}": "${valueBefore}" -> "${valueAfter}"`);
  1351. }
  1352. this.forEachPlugin((plugin) => {
  1353. if(typeof plugin.onVariableSet === "function") {
  1354. plugin.onVariableSet(key, valueBefore, valueAfter);
  1355. }
  1356. });
  1357. if(key == "monster_name") {
  1358. const combatBefore = !!(valueBefore && valueBefore!="none");
  1359. const combatAfter = !!(valueAfter && valueAfter!="none");
  1360. if(!combatBefore && combatAfter) {
  1361. this.onCombatStart();
  1362. }
  1363. else if(combatBefore && !combatAfter) {
  1364. this.onCombatEnd();
  1365. }
  1366. }
  1367. }
  1368.  
  1369. onChat(data) {
  1370. if(this.debug) {
  1371. console.log(`IP+ onChat`, data);
  1372. }
  1373. this.forEachPlugin((plugin) => {
  1374. if(typeof plugin.onChat === "function") {
  1375. plugin.onChat(data);
  1376. }
  1377. });
  1378. }
  1379.  
  1380. onPanelChanged(panelBefore, panelAfter) {
  1381. if(this.debug) {
  1382. console.log(`IP+ onPanelChanged "${panelBefore}" -> "${panelAfter}"`);
  1383. }
  1384. if(panelAfter === "idlepixelplus") {
  1385. this.refreshPanel("idlepixelplus");
  1386. }
  1387. this.forEachPlugin((plugin) => {
  1388. if(typeof plugin.onPanelChanged === "function") {
  1389. plugin.onPanelChanged(panelBefore, panelAfter);
  1390. }
  1391. });
  1392. }
  1393.  
  1394. }
  1395.  
  1396. // Add to window and init
  1397. window.IdlePixelPlusPlugin = IdlePixelPlusPlugin;
  1398. window.IdlePixelPlus = new IdlePixelPlus();
  1399.  
  1400. window.IdlePixelPlus.customChatCommands["help"] = (command, data='') => {
  1401. let help;
  1402. if(data && data!="help") {
  1403. let helpContent = window.IdlePixelPlus.customChatHelp[data.trim()] || "No help content was found for this command.";
  1404. help = `
  1405. <div class="ipp-chat-command-help">
  1406. <strong><u>Command Help:</u></strong><br />
  1407. <strong>/${data}:</strong> <span>${helpContent}</span>
  1408. </div>
  1409. `;
  1410. }
  1411. else {
  1412. help = `
  1413. <div class="ipp-chat-command-help">
  1414. <strong><u>Command Help:</u></strong><br />
  1415. <strong>Available Commands:</strong> <span>${Object.keys(window.IdlePixelPlus.customChatCommands).sort().map(s => "/"+s).join(" ")}</span><br />
  1416. <span>Use the /help command for more information about a specific command: /help &lt;command&gt;</span>
  1417. </div>
  1418. `;
  1419. }
  1420. $("#chat-area").append(help);
  1421. if(Chat._auto_scroll) {
  1422. $("#chat-area").scrollTop($("#chat-area")[0].scrollHeight);
  1423. }
  1424. };
  1425.  
  1426. const SHRUG = "¯\\_(ツ)_/¯";
  1427. window.IdlePixelPlus.registerCustomChatCommand(["shrug", "rshrug"], (command, data='') => {
  1428. data=data.replace(/~/g, " ");
  1429. const margin = SHRUG.length + 1;
  1430. data = data.substring(0, 250-margin);
  1431. window.IdlePixelPlus.sendMessage(`CHAT=${data} ${SHRUG}`);
  1432. }, `Adds a ${SHRUG} to the end of your chat message.<br /><strong>Usage:</strong> /%COMMAND% &lt;message&gt;`);
  1433.  
  1434. window.IdlePixelPlus.registerCustomChatCommand("lshrug", (command, data='') => {
  1435. data=data.replace(/~/g, " ");
  1436. const margin = SHRUG.length + 1;
  1437. data = data.substring(0, 250-margin);
  1438. window.IdlePixelPlus.sendMessage(`CHAT=${SHRUG} ${data}`);
  1439. }, `Adds a ${SHRUG} to the beginning of your chat message.<br /><strong>Usage:</strong> /%COMMAND% &lt;message&gt;`);
  1440.  
  1441. window.IdlePixelPlus.registerCustomChatCommand("clear", (command, data='') => {
  1442. $("#chat-area").empty();
  1443. }, `Clears all messages in chat.`);
  1444.  
  1445.  
  1446. internal.init.call(window.IdlePixelPlus);
  1447.  
  1448. })();