您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Idle-Pixel plugin framework
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/441206/1112539/IdlePixel%2B.js
// ==UserScript== // @name IdlePixel+ // @namespace com.anwinity.idlepixel // @version 1.2.2 // @description Idle-Pixel plugin framework // @author Anwinity // @match *://idle-pixel.com/login/play* // @grant none // ==/UserScript== (function() { 'use strict'; const VERSION = "1.2.2"; if(window.IdlePixelPlus) { // already loaded return; } const LOCAL_STORAGE_KEY_DEBUG = "IdlePixelPlus:debug"; const CONFIG_TYPES_LABEL = ["label"]; const CONFIG_TYPES_BOOLEAN = ["boolean", "bool", "checkbox"]; const CONFIG_TYPES_INTEGER = ["integer", "int"]; const CONFIG_TYPES_FLOAT = ["number", "num", "float"]; const CONFIG_TYPES_STRING = ["string", "text"]; const CONFIG_TYPES_SELECT = ["select"]; const CONFIG_TYPES_COLOR = ["color"]; const CHAT_COMMAND_NO_OVERRIDE = ["help", "mute", "ban", "pm"]; function createCombatZoneObjects() { const fallback = { field: { id: "field", commonMonsters: [ "Chickens", "Rats", "Spiders" ], rareMonsters: [ "Lizards", "Bees" ], energyCost: 50, fightPointCost: 300 }, blood_field: { id: "blood_field", blood: true, commonMonsters: [ "Blood Chickens", "Blood Rats", "Blood Spiders" ], rareMonsters: [ "Blood Lizards", "Blood Bees" ], energyCost: 5000, fightPointCost: 2000 }, forest: { id: "forest", commonMonsters: [ "Snakes", "Ants", "Wolves" ], rareMonsters: [ "Ents", "Thief" ], energyCost: 200, fightPointCost: 600 }, cave: { id: "cave", commonMonsters: [ "Bears", "Goblins", "Bats" ], rareMonsters: [ "Skeletons" ], energyCost: 500, fightPointCost: 900 }, volcano: { id: "volcano", commonMonsters: [ "Fire Hawk", "Fire Snake", "Fire Golem" ], rareMonsters: [ "Fire Witch" ], energyCost: 1000, fightPointCost: 1500 }, northern_field: { id: "northern_field", commonMonsters: [ "Ice Hawk", "Ice Witch", "Golem" ], rareMonsters: [ "Yeti" ], energyCost: 3000, fightPointCost: 2000 } }; try { const normalCode = Combat._modal_load_area_data.toString().split(/\r?\n/g); const bloodCode = Combat._modal_load_blood_area_data.toString().split(/\r?\n/g); const zones = {}; [false, true].forEach(blood => { const code = blood ? bloodCode : normalCode; let foundSwitch = false; let endSwitch = false; let current = null; code.forEach(line => { if(endSwitch) { return; } if(!foundSwitch) { if(line.includes("switch(area)")) { foundSwitch = true; } } else { line = line.trim(); if(foundSwitch && !endSwitch && !current && line=='}') { endSwitch = true; } else if(/case /.test(line)) { // start of zone data let zoneId = line.replace(/^case\s+"/, "").replace(/":.*$/, ""); current = zones[zoneId] = {id: zoneId, blood: blood}; } else if(line.startsWith("break;")) { // end of zone data current = null; } else if(current) { if(line.startsWith("common_monsters_array")) { current.commonMonsters = line .replace("common_monsters_array = [", "") .replace("];", "") .split(/\s*,\s*/g) .map(s => s.substring(1, s.length-1)); } else if(line.startsWith("rare_monsters_array")) { current.rareMonsters = line .replace("rare_monsters_array = [", "") .replace("];", "") .split(/\s*,\s*/g) .map(s => s.substring(1, s.length-1)); } else if(line.startsWith("energy")) { current.energyCost = parseInt(line.match(/\d+/)[0]); } else if(line.startsWith("fightpoints")) { current.fightPointCost = parseInt(line.match(/\d+/)[0]); } } } }); }); if(!zones || !Object.keys(zones).length) { console.error("IdlePixelPlus: Could not parse combat zone data, using fallback."); return fallback; } return zones; } catch(err) { console.error("IdlePixelPlus: Could not parse combat zone data, using fallback.", err); return fallback; } } function createOreObjects() { const ores = { stone: { smeltable:false, bar: null }, copper: { smeltable:true, smeltTime: 3, bar: "bronze_bar" }, iron: { smeltable:true, smeltTime: 6, bar: "iron_bar" }, silver: { smeltable:true, smeltTIme: 15, bar: "silver_bar" }, gold: { smeltable:true, smeltTIme: 50, bar: "gold_bar" }, promethium: { smeltable:true, smeltTIme: 100, bar: "promethium_bar" } }; try { Object.keys(ores).forEach(id => { const obj = ores[id]; obj.id = id; obj.oil = Crafting.getOilPerBar(id); obj.charcoal = Crafting.getCharcoalPerBar(id); }); } catch(err) { console.error("IdlePixelPlus: Could not create ore data. This could adversely affect related functionality.", err); } return ores; } function createSeedObjects() { // hardcoded for now. return { dotted_green_leaf_seeds: { id: "dotted_green_leaf_seeds", level: 1, stopsDying: 15, time: 15, bonemealCost: 0 }, stardust_seeds: { id: "stardust_seeds", level: 8, stopsDying: 0, time: 20, bonemealCost: 0 }, green_leaf_seeds: { id: "green_leaf_seeds", level: 10, stopsDying: 25, time: 30, bonemealCost: 0 }, lime_leaf_seeds: { id: "lime_leaf_seeds", level: 25, stopsDying: 40, time: 1*60, bonemealCost: 1 }, gold_leaf_seeds: { id: "gold_leaf_seeds", level: 50, stopsDying: 60, time: 2*60, bonemealCost: 10 }, crystal_leaf_seeds: { id: "crystal_leaf_seeds", level: 70, stopsDying: 80, time: 5*60, bonemealCost: 25 }, red_mushroom_seeds: { id: "red_mushroom_seeds", level: 1, stopsDying: 0, time: 5, bonemealCost: 0 }, tree_seeds: { id: "tree_seeds", level: 10, stopsDying: 25, time: 5*60, bonemealCost: 10 }, oak_tree_seeds: { id: "oak_tree_seeds", level: 25, stopsDying: 40, time: 4*60, bonemealCost: 25 }, willow_tree_seeds: { id: "willow_tree_seeds", level: 37, stopsDying: 55, time: 8*60, bonemealCost: 50 }, maple_tree_seeds: { id: "maple_tree_seeds", level: 50, stopsDying: 65, time: 12*60, bonemealCost: 120 }, stardust_tree_seeds: { id: "stardust_tree_seeds", level: 65, stopsDying: 80, time: 15*60, bonemealCost: 150 }, pine_tree_seeds: { id: "pine_tree_seeds", level: 70, stopsDying: 85, time: 17*60, bonemealCost: 180 } }; } function createSpellObjects() { const spells = {}; Object.keys(Magic.spell_info).forEach(id => { const info = Magic.spell_info[id]; spells[id] = { id: id, manaCost: info.mana_cost, magicBonusRequired: info.magic_bonus }; }); return spells; } const INFO = { ores: createOreObjects(), seeds: createSeedObjects(), combatZones: createCombatZoneObjects(), spells: createSpellObjects() }; function logFancy(s, color="#00f7ff") { console.log("%cIdlePixelPlus: %c"+s, `color: ${color}; font-weight: bold; font-size: 12pt;`, "color: black; font-weight: normal; font-size: 10pt;"); } class IdlePixelPlusPlugin { constructor(id, opts) { if(typeof id !== "string") { throw new TypeError("IdlePixelPlusPlugin constructor takes the following arguments: (id:string, opts?:object)"); } this.id = id; this.opts = opts || {}; this.config = null; } getConfig(name) { if(!this.config) { IdlePixelPlus.loadPluginConfigs(this.id); } if(this.config) { return this.config[name]; } } /* onConfigsChanged() { } onLogin() { } onMessageReceived(data) { } onVariableSet(key, valueBefore, valueAfter) { } onChat(data) { } onPanelChanged(panelBefore, panelAfter) { } onCombatStart() { } onCombatEnd() { } onCustomMessageReceived(player, content, callbackId) { } onCustomMessagePlayerOffline(player, content) { } */ } const internal = { init() { const self = this; $("head").append(` <style> .ipp-chat-command-help { padding: 0.5em 0; } .ipp-chat-command-help:first-child { padding-top: 0; } .ipp-chat-command-help:last-child { padding-bottom: 0; } dialog.ipp-dialog { background-color: white; border: 1px solid rgba(0, 0, 0, 0.2); width: 500px; max-width: 800px; border-radius: 5px; display: flex; flex-direction: column; justify-content: flex-start; } dialog.ipp-dialog > div { width: 100%; } dialog.ipp-dialog > .ipp-dialog-header > h4 { margin-bottom: 0; } dialog.ipp-dialog > .ipp-dialog-header { border-bottom: 1px solid rgba(0, 0, 0, 0.2); padding-bottom: 0.25em; } dialog.ipp-dialog > .ipp-dialog-actions { padding-top: 0.25em; padding-bottom: 0.25em; } dialog.ipp-dialog > .ipp-dialog-actions { border-top: 1px solid rgba(0, 0, 0, 0.2); padding-top: 0.25em; text-align: right; } dialog.ipp-dialog > .ipp-dialog-actions > button { margin: 4px; } </style> `); // hook into websocket messages const hookIntoOnMessage = () => { try { const original_onmessage = window.websocket.connected_socket.onmessage; if(typeof original_onmessage === "function") { window.websocket.connected_socket.onmessage = function(event) { original_onmessage.apply(window.websocket.connected_socket, arguments); self.onMessageReceived(event.data); } return true; } else { return false; } } catch(err) { console.error("Had trouble hooking into websocket..."); return false; } }; $(function() { if(!hookIntoOnMessage()) { // try once more setTimeout(hookIntoOnMessage, 40); } }); // hook into Chat.send const original_chat_send = Chat.send; Chat.send = function() { const input = $("#chat-area-input"); let message = input.val(); if(message.length == 0) { return; } if(message.startsWith("/")) { const space = message.indexOf(" "); let command; let data; if(space <= 0) { command = message.substring(1); data = ""; } else { command = message.substring(1, space); data = message.substring(space+1); } if(window.IdlePixelPlus.handleCustomChatCommand(command, data)) { input.val(""); } else { original_chat_send(); } } else { original_chat_send(); } }; // hook into Items.set, which is where var_ values are set const original_items_set = Items.set; Items.set = function(key, value) { let valueBefore = window["var_"+key]; original_items_set.apply(this, arguments); let valueAfter = window["var_"+key]; self.onVariableSet(key, valueBefore, valueAfter); } // hook into switch_panels, which is called when the main panel is changed. This is also used for custom panels. const original_switch_panels = window.switch_panels; window.switch_panels = function(id) { let panelBefore = Globals.currentPanel; if(panelBefore && panelBefore.startsWith("panel-")) { panelBefore = panelBefore.substring("panel-".length); } self.hideCustomPanels(); original_switch_panels.apply(this, arguments); let panelAfter = Globals.currentPanel; if(panelAfter && panelAfter.startsWith("panel-")) { panelAfter = panelAfter.substring("panel-".length); } self.onPanelChanged(panelBefore, panelAfter); } // create plugin menu item and panel const lastMenuItem = $("#menu-bar-buttons > .hover-menu-bar-item").last(); lastMenuItem.after(` <div onclick="IdlePixelPlus.setPanel('idlepixelplus')" class="hover hover-menu-bar-item"> <img id="menu-bar-idlepixelplus-icon" src="https://anwinity.com/idlepixelplus/plugins.png"> PLUGINS </div> `); self.addPanel("idlepixelplus", "IdlePixel+ Plugins", function() { let content = ` <style> .idlepixelplus-plugin-box { display: block; position: relative; padding: 0.25em; color: white; background-color: rgb(107, 107, 107); border: 1px solid black; border-radius: 6px; margin-bottom: 0.5em; } .idlepixelplus-plugin-box .idlepixelplus-plugin-settings-button { position: absolute; right: 2px; top: 2px; cursor: pointer; } .idlepixelplus-plugin-box .idlepixelplus-plugin-config-section { display: grid; grid-template-columns: minmax(100px, min-content) 1fr; row-gap: 0.5em; column-gap: 0.5em; white-space: nowrap; } </style> `; self.forEachPlugin(plugin => { let id = plugin.id; let name = "An IdlePixel+ Plugin!"; let description = ""; let author = "unknown"; if(plugin.opts.about) { let about = plugin.opts.about; name = about.name || name; description = about.description || description; author = about.author || author; } content += ` <div id="idlepixelplus-plugin-box-${id}" class="idlepixelplus-plugin-box"> <strong><u>${name||id}</u></strong> (by ${author})<br /> <span>${description}</span><br /> <div class="idlepixelplus-plugin-config-section" style="display: none"> <hr style="grid-column: span 2"> `; if(plugin.opts.config && Array.isArray(plugin.opts.config)) { plugin.opts.config.forEach(cfg => { if(CONFIG_TYPES_LABEL.includes(cfg.type)) { content += `<h5 style="grid-column: span 2; margin-bottom: 0; font-weight: 600">${cfg.label}</h5>`; } else if(CONFIG_TYPES_BOOLEAN.includes(cfg.type)) { content += ` <div> <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label> </div> <div> <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="checkbox" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" /> </div> `; } else if(CONFIG_TYPES_INTEGER.includes(cfg.type)) { content += ` <div> <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label> </div> <div> <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="number" step="1" min="${cfg.min || ''}" max="${cfg.max || ''}" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" /> </div> `; } else if(CONFIG_TYPES_FLOAT.includes(cfg.type)) { content += ` <div> <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label> </div> <div> <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="number" step="${cfg.step || ''}" min="${cfg.min || ''}" max="${cfg.max || ''}" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" /> </div> `; } else if(CONFIG_TYPES_STRING.includes(cfg.type)) { content += ` <div> <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label> </div> <div> <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="text" maxlength="${cfg.max || ''}" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" /> </div> `; } else if(CONFIG_TYPES_COLOR.includes(cfg.type)) { content += ` <div> <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label> </div> <div> <input id="idlepixelplus-config-${plugin.id}-${cfg.id}" type="color" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)" /> </div> `; } else if(CONFIG_TYPES_SELECT.includes(cfg.type)) { content += ` <div> <label for="idlepixelplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label> </div> <div> <select id="idlepixelplus-config-${plugin.id}-${cfg.id}" onchange="IdlePixelPlus.setPluginConfigUIDirty('${id}', true)"> `; if(cfg.options && Array.isArray(cfg.options)) { cfg.options.forEach(option => { if(typeof option === "string") { content += `<option value="${option}">${option}</option>`; } else { content += `<option value="${option.value}">${option.label || option.value}</option>`; } }); } content += ` </select> </div> `; } }); content += ` <div style="grid-column: span 2"> <button id="idlepixelplus-configbutton-${plugin.id}-reload" onclick="IdlePixelPlus.loadPluginConfigs('${id}')">Reload</button> <button id="idlepixelplus-configbutton-${plugin.id}-apply" onclick="IdlePixelPlus.savePluginConfigs('${id}')">Apply</button> </div> `; } content += "</div>"; if(plugin.opts.config) { content += ` <div class="idlepixelplus-plugin-settings-button"> <button onclick="$('#idlepixelplus-plugin-box-${id} .idlepixelplus-plugin-config-section').toggle()">Settings</button> </div>`; } content += "</div>"; }); return content; }); $("#chat-area-input").attr("autocomplete", "off"); logFancy(`(v${self.version}) initialized.`); } }; class IdlePixelPlus { constructor() { this.version = VERSION; this.plugins = {}; this.panels = {}; this.debug = false; this.info = INFO; this.nextUniqueId = 1; this.customMessageCallbacks = {}; this.customChatCommands = { help: (command, data) => { console.log("help", command, data); } }; this.customChatHelp = {}; this.customDialogOptions = {}; if(localStorage.getItem(LOCAL_STORAGE_KEY_DEBUG) == "1") { this.debug = true; } } getCustomDialogData(id) { const el = document.querySelector(`dialog#${id}.ipp-dialog`); if(el) { const result = {}; $(el).find("[data-key]").each(function() { const dataElement = $(this); const dataKey = dataElement.attr("data-key"); if(["INPUT", "SELECT", "TEXTAREA"].includes(dataElement.prop("tagName"))) { result[dataKey] = dataElement.val(); } else { result[dataKey] = dataElement.text(); } }); return result; } } openCustomDialog(id, noEvent=false) { this.closeCustomDialog(id, true); const el = document.querySelector(`dialog#${id}.ipp-dialog`); if(el) { el.style.display = ""; el.showModal(); const opts = this.customDialogOptions[id]; if(!noEvent && opts && typeof opts.onOpen === "function") { opts.onOpen(opts); } } } closeCustomDialog(id, noEvent=false) { const el = document.querySelector(`dialog#${id}.ipp-dialog`); if(el) { el.close(); el.style.display = "none"; const opts = this.customDialogOptions[id]; if(!noEvent && opts && typeof opts.onClose === "function") { opts.onClose(opts); } } } destroyCustomDialog(id, noEvent=false) { const el = document.querySelector(`dialog#${id}.ipp-dialog`); if(el) { el.remove(); const opts = this.customDialogOptions[id]; if(!noEvent && opts && typeof opts.onDestroy === "function") { opts.onDestroy(opts); } } delete this.customDialogOptions[id]; } createCustomDialog(id, opts={}) { const self = this; this.destroyCustomDialog(id); this.customDialogOptions[id] = opts; const el = $("body").append(` <dialog id="${id}" class="ipp-dialog" style="display: none"> <div class="ipp-dialog-header"> <h4>${opts.title||''}</h4> </div> <div class="ipp-dialog-content"></div> <div class="ipp-dialog-actions"></div> </dialog> `); const headerElement = el.find(".ipp-dialog-header"); const contentElement = el.find(".ipp-dialog-content"); const actionsElement = el.find(".ipp-dialog-actions"); if(!opts.title) { headerElement.hide(); } if(typeof opts.content === "string") { contentElement.append(opts.content); } let actions = opts.actions; if(actions) { if(!Array.isArray(actions)) { actions = [actions]; } actions.forEach(action => { let label; let primary = false; if(typeof action === "string") { label = action; } else { label = action.label || action.action; primary = action.primary===true; action = action.action; } actionsElement.append(`<button data-action="${action}" class="${primary?'background-primary':''}">${label}</button>`); }); actionsElement.find("button").on("click", function(e) { if(typeof opts.onAction === "function") { e.stopPropagation(); const button = $(this); const buttonAction = button.attr("data-action"); const data = self.getCustomDialogData(id); const actionReturn = opts.onAction(buttonAction, data); if(actionReturn) { self.closeCustomDialog(id); } } }); } else { el.find(".ipp-dialog-actions").hide(); } el.click(function(e) { const rect = e.target.getBoundingClientRect(); const inside = rect.top <= e.clientY && rect.left <= e.clientX && e.clientX <= rect.left + rect.width && e.clientY <= rect.top + rect.height; if(!inside) { self.closeCustomDialog(id); e.stopPropagation(); } }); if(typeof opts.onCreate === "function") { opts.onCreate(); } if(opts.openImmediately === true) { this.openCustomDialog(id); } } registerCustomChatCommand(command, f, help) { if(Array.isArray(command)) { command.forEach(cmd => this.registerCustomChatCommand(cmd, f, help)); return; } if(typeof command !== "string" || typeof f !== "function") { throw new TypeError("IdlePixelPlus.registerCustomChatCommand takes the following arguments: (command:string, f:function)"); } if(CHAT_COMMAND_NO_OVERRIDE.includes(command)) { throw new Error(`Cannot override the following chat commands: ${CHAT_COMMAND_NO_OVERRIDE.join(", ")}`); } if(command in this.customChatCommands) { console.warn(`IdlePixelPlus: re-registering custom chat command "${command}" which already exists.`); } this.customChatCommands[command] = f; if(help && typeof help === "string") { this.customChatHelp[command] = help.replace(/%COMMAND%/g, command); } else { delete this.customChatHelp[command]; } } handleCustomChatCommand(command, message) { // return true if command handler exists, false otherwise const f = this.customChatCommands[command]; if(typeof f === "function") { try { f(command, message); } catch(err) { console.error(`Error executing custom command "${command}"`, err); } return true; } return false; } uniqueId() { return this.nextUniqueId++; } setDebug(debug) { if(debug) { this.debug = true; localStorage.setItem(LOCAL_STORAGE_KEY_DEBUG, "1"); } else { this.debug = false; localStorage.removeItem(LOCAL_STORAGE_KEY_DEBUG); } } getVar(name, type) { let s = window[`var_${name}`]; if(type) { switch(type) { case "int": case "integer": return parseInt(s); case "number": case "float": return parseFloat(s); case "boolean": case "bool": if(s=="true") return true; if(s=="false") return false; return undefined; } } return s; } getVarOrDefault(name, defaultValue, type) { let s = window[`var_${name}`]; if(s==null || typeof s === "undefined") { return defaultValue; } if(type) { let value; switch(type) { case "int": case "integer": value = parseInt(s); return isNaN(value) ? defaultValue : value; case "number": case "float": value = parseFloat(s); return isNaN(value) ? defaultValue : value; case "boolean": case "bool": if(s=="true") return true; if(s=="false") return false; return defaultValue; } } return s; } setPluginConfigUIDirty(id, dirty) { if(typeof id !== "string" || typeof dirty !== "boolean") { throw new TypeError("IdlePixelPlus.setPluginConfigUIDirty takes the following arguments: (id:string, dirty:boolean)"); } const plugin = this.plugins[id]; const button = $(`#idlepixelplus-configbutton-${plugin.id}-apply`); if(button) { button.prop("disabled", !(dirty)); } } loadPluginConfigs(id) { if(typeof id !== "string") { throw new TypeError("IdlePixelPlus.reloadPluginConfigs takes the following arguments: (id:string)"); } const plugin = this.plugins[id]; const config = {}; let stored; try { stored = JSON.parse(localStorage.getItem(`idlepixelplus.${id}.config`) || "{}"); } catch(err) { console.error(`Failed to load configs for plugin with id "${id} - will use defaults instead."`); stored = {}; } if(plugin.opts.config && Array.isArray(plugin.opts.config)) { plugin.opts.config.forEach(cfg => { const el = $(`#idlepixelplus-config-${plugin.id}-${cfg.id}`); let value = stored[cfg.id]; if(value==null || typeof value === "undefined") { value = cfg.default; } config[cfg.id] = value; if(el) { if(CONFIG_TYPES_BOOLEAN.includes(cfg.type) && typeof value === "boolean") { el.prop("checked", value); } else if(CONFIG_TYPES_INTEGER.includes(cfg.type) && typeof value === "number") { el.val(value); } else if(CONFIG_TYPES_FLOAT.includes(cfg.type) && typeof value === "number") { el.val(value); } else if(CONFIG_TYPES_STRING.includes(cfg.type) && typeof value === "string") { el.val(value); } else if(CONFIG_TYPES_SELECT.includes(cfg.type) && typeof value === "string") { el.val(value); } else if(CONFIG_TYPES_COLOR.includes(cfg.type) && typeof value === "string") { el.val(value); } } }); } plugin.config = config; this.setPluginConfigUIDirty(id, false); if(typeof plugin.onConfigsChanged === "function") { plugin.onConfigsChanged(); } } savePluginConfigs(id) { if(typeof id !== "string") { throw new TypeError("IdlePixelPlus.savePluginConfigs takes the following arguments: (id:string)"); } const plugin = this.plugins[id]; const config = {}; if(plugin.opts.config && Array.isArray(plugin.opts.config)) { plugin.opts.config.forEach(cfg => { const el = $(`#idlepixelplus-config-${plugin.id}-${cfg.id}`); let value; if(CONFIG_TYPES_BOOLEAN.includes(cfg.type)) { config[cfg.id] = el.is(":checked"); } else if(CONFIG_TYPES_INTEGER.includes(cfg.type)) { config[cfg.id] = parseInt(el.val()); } else if(CONFIG_TYPES_FLOAT.includes(cfg.type)) { config[cfg.id] = parseFloat(el.val()); } else if(CONFIG_TYPES_STRING.includes(cfg.type)) { config[cfg.id] = el.val(); } else if(CONFIG_TYPES_SELECT.includes(cfg.type)) { config[cfg.id] = el.val(); } else if(CONFIG_TYPES_COLOR.includes(cfg.type)) { config[cfg.id] = el.val(); } }); } plugin.config = config; localStorage.setItem(`idlepixelplus.${id}.config`, JSON.stringify(config)); this.setPluginConfigUIDirty(id, false); if(typeof plugin.onConfigsChanged === "function") { plugin.onConfigsChanged(); } } addPanel(id, title, content) { if(typeof id !== "string" || typeof title !== "string" || (typeof content !== "string" && typeof content !== "function") ) { throw new TypeError("IdlePixelPlus.addPanel takes the following arguments: (id:string, title:string, content:string|function)"); } const panels = $("#panels"); panels.append(` <div id="panel-${id}" style="display: none"> <h1>${title}</h1> <hr> <div class="idlepixelplus-panel-content"></div> </div> `); this.panels[id] = { id: id, title: title, content: content }; this.refreshPanel(id); } refreshPanel(id) { if(typeof id !== "string") { throw new TypeError("IdlePixelPlus.refreshPanel takes the following arguments: (id:string)"); } const panel = this.panels[id]; if(!panel) { throw new TypeError(`Error rendering panel with id="${id}" - panel has not be added.`); } let content = panel.content; if(!["string", "function"].includes(typeof content)) { throw new TypeError(`Error rendering panel with id="${id}" - panel.content must be a string or a function returning a string.`); } if(typeof content === "function") { content = content(); if(typeof content !== "string") { throw new TypeError(`Error rendering panel with id="${id}" - panel.content must be a string or a function returning a string.`); } } const panelContent = $(`#panel-${id} .idlepixelplus-panel-content`); panelContent.html(content); if(id === "idlepixelplus") { this.forEachPlugin(plugin => { this.loadPluginConfigs(plugin.id); }); } } registerPlugin(plugin) { if(!(plugin instanceof IdlePixelPlusPlugin)) { throw new TypeError("IdlePixelPlus.registerPlugin takes the following arguments: (plugin:IdlePixelPlusPlugin)"); } if(plugin.id in this.plugins) { throw new Error(`IdlePixelPlusPlugin with id "${plugin.id}" is already registered. Make sure your plugin id is unique!`); } this.plugins[plugin.id] = plugin; this.loadPluginConfigs(plugin.id); let versionString = plugin.opts&&plugin.opts.about&&plugin.opts.about.version ? ` (v${plugin.opts.about.version})` : ""; logFancy(`registered plugin "${plugin.id}"${versionString}`); } forEachPlugin(f) { if(typeof f !== "function") { throw new TypeError("IdlePixelPlus.forEachPlugin takes the following arguments: (f:function)"); } Object.values(this.plugins).forEach(plugin => { try { f(plugin); } catch(err) { console.error(`Error occurred while executing function for plugin "${plugin.id}."`); console.error(err); } }); } setPanel(panel) { if(typeof panel !== "string") { throw new TypeError("IdlePixelPlus.setPanel takes the following arguments: (panel:string)"); } window.switch_panels(`panel-${panel}`); } sendMessage(message) { if(typeof message !== "string") { throw new TypeError("IdlePixelPlus.sendMessage takes the following arguments: (message:string)"); } if(window.websocket && window.websocket.connected_socket && window.websocket.connected_socket.readyState==1) { window.websocket.connected_socket.send(message); } } showToast(title, content) { show_toast(title, content); } hideCustomPanels() { Object.values(this.panels).forEach((panel) => { const el = $(`#panel-${panel.id}`); if(el) { el.css("display", "none"); } }); } onMessageReceived(data) { if(this.debug) { console.log(`IP+ onMessageReceived: ${data}`); } if(data) { this.forEachPlugin((plugin) => { if(typeof plugin.onMessageReceived === "function") { plugin.onMessageReceived(data); } }); if(data.startsWith("VALID_LOGIN")) { this.onLogin(); } else if(data.startsWith("CHAT=")) { const split = data.substring("CHAT=".length).split("~"); const chatData = { username: split[0], sigil: split[1], tag: split[2], level: parseInt(split[3]), message: split[4] }; this.onChat(chatData); // CHAT=anwinity~none~none~1565~test } else if(data.startsWith("CUSTOM=")) { const customData = data.substring("CUSTOM=".length); const tilde = customData.indexOf("~"); if(tilde > 0) { const fromPlayer = customData.substring(0, tilde); const content = customData.substring(tilde+1); this.onCustomMessageReceived(fromPlayer, content); } } } } deleteCustomMessageCallback(callbackId) { if(this.debug) { console.log(`IP+ deleteCustomMessageCallback`, callbackId); } delete this.customMessageCallbacks[callbackId]; } requestPluginManifest(player, callback, pluginId) { if(typeof pluginId === "string") { pluginId = [pluginId]; } if(Array.isArray(pluginId)) { pluginId = JSON.stringify(pluginId); } this.sendCustomMessage(player, { content: "PLUGIN_MANIFEST" + (pluginId ? `:${pluginId}` : ''), onResponse: function(respPlayer, content) { if(typeof callback === "function") { callback(respPlayer, JSON.parse(content)); } else { console.log(`Plugin Manifest: ${respPlayer}`, content); } }, onOffline: function(respPlayer, content) { if(typeof callback === "function") { callback(respPlayer, false); } }, timeout: 10000 }); } sendCustomMessage(toPlayer, opts) { if(this.debug) { console.log(`IP+ sendCustomMessage`, toPlayer, opts); } const reply = !!(opts.callbackId); const content = typeof opts.content === "string" ? opts.content : JSON.stringify(opts.content); const callbackId = reply ? opts.callbackId : this.uniqueId(); const responseHandler = typeof opts.onResponse === "function" ? opts.onResponse : null; const offlineHandler = opts.onOffline===true ? () => { this.deleteCustomMessageCallback(callbackId); } : (typeof opts.onOffline === "function" ? opts.onOffline : null); const timeout = typeof opts.timeout === "number" ? opts.timeout : -1; if(responseHandler || offlineHandler) { const handler = { id: callbackId, player: toPlayer, responseHandler: responseHandler, offlineHandler: offlineHandler, timeout: typeof timeout === "number" ? timeout : -1, }; if(callbackId) { this.customMessageCallbacks[callbackId] = handler; if(handler.timeout > 0) { setTimeout(() => { this.deleteCustomMessageCallback(callbackId); }, handler.timeout); } } } const message = `CUSTOM=${toPlayer}~IPP${reply?'R':''}${callbackId}:${content}`; if(message.length > 255) { console.warn("The resulting websocket message from IdlePixelPlus.sendCustomMessage has a length limit of 255 characters. Recipients may not receive the full message!"); } this.sendMessage(message); } onCustomMessageReceived(fromPlayer, content) { if(this.debug) { console.log(`IP+ onCustomMessageReceived`, fromPlayer, content); } const offline = content == "PLAYER_OFFLINE"; let callbackId = null; let originalCallbackId = null; let reply = false; const ippMatcher = content.match(/^IPP(\w+):/); if(ippMatcher) { originalCallbackId = callbackId = ippMatcher[1]; let colon = content.indexOf(":"); content = content.substring(colon+1); if(callbackId.startsWith("R")) { callbackId = callbackId.substring(1); reply = true; } } // special built-in messages if(content.startsWith("PLUGIN_MANIFEST")) { const manifest = {}; let filterPluginIds = null; if(content.includes(":")) { content = content.substring("PLUGIN_MANIFEST:".length); filterPluginIds = JSON.parse(content).map(s => s.replace("~", "")); } this.forEachPlugin(plugin => { let id = plugin.id.replace("~", ""); if(filterPluginIds && !filterPluginIds.includes(id)) { return; } let version = "unknown"; if(plugin.opts && plugin.opts.about && plugin.opts.about.version) { version = plugin.opts.about.version.replace("~", ""); } manifest[id] = version; }); manifest.IdlePixelPlus = IdlePixelPlus.version; this.sendCustomMessage(fromPlayer, { content: manifest, callbackId: callbackId }); return; } const callbacks = this.customMessageCallbacks; if(reply) { const handler = callbacks[callbackId]; if(handler && typeof handler.responseHandler === "function") { try { if(handler.responseHandler(fromPlayer, content, originalCallbackId)) { this.deleteCustomMessageCallback(callbackId); } } catch(err) { console.error("Error executing custom message response handler.", {player: fromPlayer, content: content, handler: handler}); } } } else if(offline) { Object.values(callbacks).forEach(handler => { try { if(handler.player.toLowerCase()==fromPlayer.toLowerCase() && typeof handler.offlineHandler === "function" && handler.offlineHandler(fromPlayer, content)) { this.deleteCustomMessageCallback(handler.id); } } catch(err) { console.error("Error executing custom message offline handler.", {player: fromPlayer, content: content, handler: handler}); } }); } if(offline) { this.onCustomMessagePlayerOffline(fromPlayer, content); } else { this.forEachPlugin((plugin) => { if(typeof plugin.onCustomMessageReceived === "function") { plugin.onCustomMessageReceived(fromPlayer, content, originalCallbackId); } }); } } onCustomMessagePlayerOffline(fromPlayer, content) { if(this.debug) { console.log(`IP+ onCustomMessagePlayerOffline`, fromPlayer, content); } this.forEachPlugin((plugin) => { if(typeof plugin.onCustomMessagePlayerOffline === "function") { plugin.onCustomMessagePlayerOffline(fromPlayer, content); } }); } onCombatStart() { if(this.debug) { console.log(`IP+ onCombatStart`); } this.forEachPlugin((plugin) => { if(typeof plugin.onCombatStart === "function") { plugin.onCombatStart(); } }); } onCombatEnd() { if(this.debug) { console.log(`IP+ onCombatEnd`); } this.forEachPlugin((plugin) => { if(typeof plugin.onCombatEnd === "function") { plugin.onCombatEnd(); } }); } onLogin() { if(this.debug) { console.log(`IP+ onLogin`); } logFancy("login detected"); this.forEachPlugin((plugin) => { if(typeof plugin.onLogin === "function") { plugin.onLogin(); } }); $("#chat-area").append(` <div class="ipp-chat-command-help"> <span><strong>FYI: </strong> Use the /help command to see information on available chat commands.</span> </div> `); if(Chat._auto_scroll) { $("#chat-area").scrollTop($("#chat-area")[0].scrollHeight); } } onVariableSet(key, valueBefore, valueAfter) { if(this.debug) { console.log(`IP+ onVariableSet "${key}": "${valueBefore}" -> "${valueAfter}"`); } this.forEachPlugin((plugin) => { if(typeof plugin.onVariableSet === "function") { plugin.onVariableSet(key, valueBefore, valueAfter); } }); if(key == "monster_name") { const combatBefore = !!(valueBefore && valueBefore!="none"); const combatAfter = !!(valueAfter && valueAfter!="none"); if(!combatBefore && combatAfter) { this.onCombatStart(); } else if(combatBefore && !combatAfter) { this.onCombatEnd(); } } } onChat(data) { if(this.debug) { console.log(`IP+ onChat`, data); } this.forEachPlugin((plugin) => { if(typeof plugin.onChat === "function") { plugin.onChat(data); } }); } onPanelChanged(panelBefore, panelAfter) { if(this.debug) { console.log(`IP+ onPanelChanged "${panelBefore}" -> "${panelAfter}"`); } if(panelAfter === "idlepixelplus") { this.refreshPanel("idlepixelplus"); } this.forEachPlugin((plugin) => { if(typeof plugin.onPanelChanged === "function") { plugin.onPanelChanged(panelBefore, panelAfter); } }); } } // Add to window and init window.IdlePixelPlusPlugin = IdlePixelPlusPlugin; window.IdlePixelPlus = new IdlePixelPlus(); window.IdlePixelPlus.customChatCommands["help"] = (command, data='') => { let help; if(data && data!="help") { let helpContent = window.IdlePixelPlus.customChatHelp[data.trim()] || "No help content was found for this command."; help = ` <div class="ipp-chat-command-help"> <strong><u>Command Help:</u></strong><br /> <strong>/${data}:</strong> <span>${helpContent}</span> </div> `; } else { help = ` <div class="ipp-chat-command-help"> <strong><u>Command Help:</u></strong><br /> <strong>Available Commands:</strong> <span>${Object.keys(window.IdlePixelPlus.customChatCommands).sort().map(s => "/"+s).join(" ")}</span><br /> <span>Use the /help command for more information about a specific command: /help <command></span> </div> `; } $("#chat-area").append(help); if(Chat._auto_scroll) { $("#chat-area").scrollTop($("#chat-area")[0].scrollHeight); } }; const SHRUG = "¯\\_(ツ)_/¯"; window.IdlePixelPlus.registerCustomChatCommand(["shrug", "rshrug"], (command, data='') => { data=data.replace(/~/g, " "); const margin = SHRUG.length + 1; data = data.substring(0, 250-margin); window.IdlePixelPlus.sendMessage(`CHAT=${data} ${SHRUG}`); }, `Adds a ${SHRUG} to the end of your chat message.<br /><strong>Usage:</strong> /%COMMAND% <message>`); window.IdlePixelPlus.registerCustomChatCommand("lshrug", (command, data='') => { data=data.replace(/~/g, " "); const margin = SHRUG.length + 1; data = data.substring(0, 250-margin); window.IdlePixelPlus.sendMessage(`CHAT=${SHRUG} ${data}`); }, `Adds a ${SHRUG} to the beginning of your chat message.<br /><strong>Usage:</strong> /%COMMAND% <message>`); window.IdlePixelPlus.registerCustomChatCommand("clear", (command, data='') => { $("#chat-area").empty(); }, `Clears all messages in chat.`); internal.init.call(window.IdlePixelPlus); })();