Цей скрипт не слід встановлювати безпосередньо. Це - бібліотека для інших скриптів для включення в мета директиву // @require https://update.greasyfork.org/scripts/461402/1280986/IdlePixel%2B%20-%20GodofNades%20Fork.js
// ==UserScript==
// @name IdlePixel+
// @namespace com.anwinity.idlepixel
// @version 1.2.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.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;
document.querySelector("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 = document.querySelector("#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 = document.querySelector("#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="document.querySelector('#idlepixelplus-plugin-box-${id} .idlepixelplus-plugin-config-section').toggle()">Settings</button>
</div>`;
}
content += "</div>";
});
return content;
});
document.querySelector("#chat-area-input").setAttribute("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 = document.querySelector("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 = document.querySelector("#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();
}
});
document.querySelector("#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) {
document.querySelector("#chat-area").scrollTop(document.querySelector("#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>
`;
}
document.querySelector("#chat-area").append(help);
if(Chat._auto_scroll) {
document.querySelector("#chat-area").scrollTop(document.querySelector("#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='') => {
document.querySelector("#chat-area").empty();
}, `Clears all messages in chat.`);
internal.init.call(window.IdlePixelPlus);
})();