// ==UserScript== // @name Panel Dodatków NI // @namespace http://the-crudness.xaa.pl/NIAddons/panel.user.js // @version 3.0.1.15 // @description Dodaje do wbudowanego panelu dodatków inne dodatki. // @author Priweejt // @include http*://*.margonem.com/ // @include http*://*.margonem.pl/ // @grant none // ==/UserScript== // ==Changelog== // 3.0 - w pewnym stopniu ogarnąłem spaghetti jakim to było, cały czas nie jest za dobrze ale już da się nad tym pracować normalnie xD // 2.6 - w sumie to samo co 2.5 // 2.5 - znów pełno zmian w API, kolejny nowy dodatek // 2.4 - pierdyliard zmian w API, nowy dodatek, usunięte na obecną chwile dodatki darrefulla bo mu hosting padł czy coś // 2.3 - nowy dodatek, naprawione loader.require // 2.2 - włączenie dodatku pokazywanie kolizji dla angielskiego margonem, bo działają już tam widgety. Trochę wewnętrznych zmian w tym skrypcie // 2.1 - dodatek lepsza walka, trochę zmian w tym kodzie żeby łatwiej się robiło dodatki (event emmitery anyone?) // 2.0.1 - pierwsza nietestowa wersja // ==/Changelog== $(document).ready(() => { new (function() { // check if the script was loaded on the new interface if (typeof Engine == "undefined" || typeof parseClanBB == "undefined" || typeof linkify == "undefined") return; const addons = this; window.priwAddons = this; window.API.priw = this; const Storage = API.Storage; const Templates = API.Templates; const protocol = window.location.protocol; /* CURSOR CSS STYLES */ this.cursors = { default: "url("+protocol+"//aldous.margonem.pl/img/gui/cursor/1.png), auto", npctalk: "url("+protocol+"//aldous.margonem.pl/img/gui/cursor/2.png), auto", fight: "url("+protocol+"//aldous.margonem.pl/img/gui/cursor/3.png), auto", grab: "url("+protocol+"//aldous.margonem.pl/img/gui/cursor/4.png), auto", pointer: "url("+protocol+"//aldous.margonem.pl/img/gui/cursor/5.png), auto" }; /* ADDON LIST holds addon data */ this.list = { /*priw_ntpd: { Eee, nie mam chyba kodu tego xD Jak ktoś BARDZO chce to mogę zrobić nowy namePL: "Notatnik", nameEN: "Notepad", descPL: "Dodaje prosty notatnik, dostępny pod torbami.", descEN: "Adds a simple notepad in the area under the bags.", urlPL: "http://priweejt.ct8.pl/NIAddons/get/notepadNI.js?", urlEN: "http://priweejt.ct8.pl/NIAddons/get/notepadNI.js?" },*/ priw_extl: { namePL: "Instalator zewnętrznych skryptów", nameEN: "External script loader", descPL: "Pozwala instalować zewnętrzne dodatki przez wpisanie adresu do skryptu.", descEN: "Allows installing external scripts from a given url.", urlPL: protocol+"//priw8.com/margo/addon/extl/extl.js", //urlPL: "http://127.0.0.1:8080/NIextLoader.js?", urlEN: protocol+"//priw8.com/margo/addon/extl/extl.js" }, priw_mmp: { namePL: "miniMap+", nameEN: "miniMap+", descPL: "Dodaje do gry wielofunkcyjną minimapę. Domyślnie otwiera sie klawiszem [R]", descEN: "Adds a multifunctional minimap. Default hotkey to open it is set to [R]. Note that it hasn't been translated to English.", //urlPL: "http://127.0.0.1:8080/mmp.js", urlPL: protocol+"//priw8.com/margo/addon/mmp/mmp.js", urlEN: protocol+"//priw8.com/margo/addon/mmp/mmp.js" }, priw_tth: { namePL: "Titan Helper+", nameEN: "Titan Helper+", descPL: "Pomimo nazwy dodatek przydatny nie tylko na tytanach. Pokazuje wiele dodatkowych informacji w walce, takich jak ile tur działają już spowolnienia i inne umki, jaki cios specjalny ładuje tytan, i wiele innych rzeczy.", descEN: "Despite the name, useful not only for titan fights. It shows a lot of extra information during battles, such as how many turns ago slowdowns and other spells were casted, what special attack is currently being charged by a titan, and numerous other nice things.", //urlPL: "http://127.0.0.1:8080/thp.js", urlPL: protocol+"//priw8.com/margo/addon/thp/thp.js", urlEN: protocol+"//priw8.com/margo/addon/thp/thp.js" }, priw_changeCharacter: { namePL: "Change character", nameEN: "Change character", descPL: "Pozwala na błyskawiczne zmienianie postaci na aktualnym świecie: wyglada tak. Kolejność postaci można zmienić klikając na którąś ppm.", descEN: "Allows quickly switching between characters on the current world: looks like this. The order in which the characters are displayed can be changed by right-clicking one of them.", urlPL: protocol+"//priw8.com/margo/addon/cc/cc.js", urlEN: protocol+"//priw8.com/margo/addon/cc/cc.js" }, priw_PartyStats: { namePL: "Statystyki grupy", nameEN: "Party stats", descPL: "Wyświetla w oknie grupy informacje o profesjach i poziomach jej członków. Powiadamia też o za dużej różnicy poziomów w grp (zaznaczając min/max lvl na czerwono - może nie działać jak należy na światach prywatnych, bo tam chyba inny wzorek).", descEN: "Displays info about party members (profession, level) in the party window. It also notifies you if the level difference of the party members is too high (by making the min/max level display red).", urlPL: protocol+"//priw8.com/margo/addon/PartyStats/PartyStats.js", urlEN: protocol+"//priw8.com/margo/addon/PartyStats/PartyStats.js" }, priw_instantTurnNotif: { namePL: "Natychmiastowe powiadomienie o turze", nameEN: "Instant turn notification", descPL: "Zamiast po 5s, dźwięk powiadomienia o turze jest odtwarzany od razu.", descEN: "Makes the turn notification sound play instantly, rather than playing it after 5 seconds.", urlPL: protocol+"//priw8.com/margo/addon/instantTurnNotif/instantTurnNotif.js", urlEN: protocol+"//priw8.com/margo/addon/instantTurnNotif/instantTurnNotif.js" }, priw_npcIconRemover: { namePL: "Usuwacz ikon NPC", nameEN: "NPC icon remover", descPL: "Usuwa ikonki leczenia, depozytu, sklepu, poczty i hotelu nad NPCami.", descEN: "Removes heal, deposit, shop, mail and inn icons from NPCs.", urlPL: protocol+"//priw8.com/margo/addon/npcIconRemover/npcIconRemover.js", urlEN: protocol+"//priw8.com/margo/addon/npcIconRemover/npcIconRemover.js" }, } this.addonIds = Object.keys(addons.list); /* COMMON FUNCTIONS */ function error(module, txt) { console.error(`priwAddonPanel::${module}: ${txt}`); } function warn(module, txt) { console.warn(`priwAddonPanel::${module}: ${txt}`); } function log(module, txt) { console.warn(`priwAddonPanel::${module}: ${txt}`); } function detourBefore(obj, key, clb, exArgs=[]) { const _prev = obj[key]; obj[key] = function() { const fullArgs = []; for (let i=0; i alert("siema") } */ this.$ = API.Templates.get("button")[0]; this.$.classList.add(options.type ? options.type : "small"); this.$.addEventListener("click", options.clb); this.setLabel(options.label); } get$() { return this.$; } setLabel(label) { this.$.querySelector(".label").innerText = label; } } /* WINDOW MODULE wrapper of the normal API.Window for convenience */ this.Window = class Window { /* =OPTIONS= WYMAGANE: /Jedno z dwóch: -txt: zawartość HTML okna -element: element do podpięcia do zawartości okna / -header: nagłówek okna OPCJONALNE -likemAlert: bool, czy okno ma posiadać klasę mAlert (default: false) -noClose: bool, czy ma zostać usunięty przycisk zamykania (default: false) -callbacks: array, taki sam jak ma mAlert w drugim argumencie (default: []) -onclose: f, funkcja jaka wykona się przy zamknięciu okna (default: none) -css: style css które będą nadane oknu */ constructor(options) { this.wnd = window.Engine.windowManager.add({ content: " ", // Needs to have a non-falseish value because NI is actual hot garbage nameWindow: options.header || " ", // Same thing here! parentObj: null, title: options.header, onclose: options.onclose }); this.wnd.addNameToWindow(options.header); this.$ = this.wnd.$[0]; this.$content = document.createElement("div"); this.$userContent = document.createElement("div"); this.$content.appendChild(this.$userContent); // this.wnd.title(options.header); if (typeof options.txt != "undefined") this.$userContent.innerHTML = options.txt; else if (typeof options.element != "undefined") this.$userContent.appendChild(options.element); if (options.likemAlert) this.$.classList.add("mAlert"); if (options.noClose) { const $close = this.getCloseBtt(); if ($close) $close.parentElement.removeChild($close); } if (options.callbacks) { for (let i=0; i -1); }; this.toggleAddon = function(id) { var storage = this.get(); if (storage.indexOf(id) > -1) { storage.splice(storage.indexOf(id), 1); } else { storage.push(id); }; this.set(storage); }; })(); /* MODULES MODULE (wowie!) gets modules from the game, or something */ this.modules = new (function() { this.init = function() { this.Interface = window.Engine.interface; // no dobra, skoro tak zrobili to fajnie this.Console = window.Engine.console; // konsola teraz tez jest dostepna }; })(); this.modules.init(); /* CONSOLE MODULE handles custom commands, allows easier logging etc */ this.console = new (function() { var self = this; var Console = null; var $content = null; var commandLine = null; var customCmds = { "!help": { handler: () => { for (var i in customCmds) { if (customCmds[i].help) window.log(""+i+" - "+customCmds[i].help); } } } }; this.loadConsoleIfNeeded = function() { if (Console == null) { if (!window.Engine.console) return false; Console = window.Engine.console; commandLine = Console.commandLine; $content = Console.wnd.$[0].querySelector(".console-content"); var _sendMessage = commandLine.sendMessage; commandLine.sendMessage = function(cmd) { if (!self.cmdExists(cmd)) { _sendMessage.apply(this, arguments); } else { window.log("> "+cmd+""); document.querySelector("#console_input").value = ""; self.parseCmd(cmd); } } } return true; } this.newCustomCmd = function(cmd) { /* przykładowe cmd: cmd = { name: "nazwa komendy", handler: n => console.log(n), [help: "opis co komanda robi"] } */ customCmds[cmd.name] = cmd; }; this.cmdExists = function(cmd) { cmd = cmd.split(" ")[0]; return customCmds[cmd] ? true : false; } this.parseCmd = function(cmd) { var args = cmd.split(" "); cmd = args.splice(0, 1)[0]; cmd = customCmds[cmd]; cmd.handler.apply(window, args); } this.log = function(txt, level) { switch (level) { case 1: txt = "Warning: "+txt+""; break; case 2: txt = "Error: "+txt+""; break; case 3: txt = "Fatal error: "+txt+""; break; } if (this.loadConsoleIfNeeded()) { var $log = document.createElement("div"); $log.classList.add("console-message"); $log.innerHTML = txt; $content.appendChild($log); if (level) { Console.showConNotif(); }; if (level == 3) Console.open(); } else { console.warn("Panel dodatków NI: błąd ładowania konsoli, nie można wyświetlić logu w grze"); console.log(txt); } } })(); /* REQUEST PARSER MODULE parsing and modifying sent/received things */ this.requestParser = new (function() { // websocket version detourBefore(Engine.communication, "send2", (query) => { return this.onRequest(query); }); // ajax version detourBefore(Engine.communication, "send", (query) => { return this.onRequest(query); }); this.onRequest = function(query) { if (query.substring(0, 4) == "init" && addons.loader.tryLoadAddons(query)) { return true; // addons need to be loaded before init is continued - return true to prevent execution of the original func } return false; } detourBefore(Engine.communication, "parseJSON", data => { this.emitEvents(data, true); }); detourAfter(Engine.communication, "parseJSON", data => { this.emitEvents(data, false); }); detourAfter(Engine.interface, "initBanners", () => { addons.emmiter.emit("game-load"); }); detourAfter(Engine.interface, "afterUnlock", () => { addons.emmiter.emit("interface-load"); }); this.emitEvents = function(data, before) { for (let i in data) { addons.emmiter.emit((before ? "before-" : "")+i, data[i]); }; addons.emmiter.emit((before ? "before-" : "")+"game-response", data); }; })(); /* LOADER MODULE crucial module that loads addons and fires some important events on the emitter */ this.loader = new (function(){ var self = this; var lastAddonId; this.addonsLoaded = 0; this.waitForInterfaceChanger = false; //eliminuje potencjalną możliwość zagryzienia się z dodatkiem SI2NI this.tryLoadAddons = function(query) { if (self.addonsLoaded < addons.addonIds.length) { const isFirstInit = query.indexOf("configGet=1") > -1; if (isFirstInit) { // If first init is being blocked, this needs to be done for proper ServerStorage loading on re-init Engine.setFirstLoadInit1(true); } window.Engine.setBlockSendNextInit(true); this.loadAddon(); return true; } else { return false; } } this.loadAddon = function() { var id = addons.addonIds[this.addonsLoaded]; lastAddonId = id; if (addons.storage.getAddonState(id)) { var addon = addons.list[id]; if (addon.beforeInstall && addon.beforeInstall()) { //failsafe jakby ktoś coś odwalił addons.storage.toggleAddon(id); return this.onAddonLoad(true); }; if (!$.cachedScript) jQuery.cachedScript=function(e,c){return c=$.extend(c||{},{dataType:"script",cache:!0,url:e}),jQuery.ajax(c)}; var url = this.getUrl(addon); window.__currentAddon = { id: addons.addonIds[this.addonsLoaded], data: addon.data }; if (url.indexOf("?") > -1) url += "&v="+((new Date()).toLocaleDateString()); else url += "?v="+((new Date()).toLocaleDateString()); $.cachedScript(url).done(this.onAddonLoad).fail((xhr) => this.failedAddonLoad(id, xhr)); //$.getScript(url).done(this.onAddonLoad).fail((xhr) => this.failedAddonLoad(id, xhr)); } else { this.onAddonLoad(true); }; }; this.onAddonLoad = function(notLoaded) { if (notLoaded !== true) { var addon = addons.list[lastAddonId]; addons.console.log("addon '"+ (_l() == "pl" ? addon.namePL : addon.nameEN) + "' loaded succesfully"); }; self.addonsLoaded++; if (self.addonsLoaded < addons.addonIds.length) { self.loadAddon(); } else { self.loadExtraAddons(); }; }; var extraLoadFinished = false; var extraAddonsLoaded = -1; this.loadExtraAddons = function(failed) { if (extraAddonsLoaded >= 0 && !failed) addons.console.log("script from "+extraAddons[extraAddonsLoaded]+" loaded succesfully"); extraAddonsLoaded++; if (extraAddonsLoaded < extraAddons.length) { var url = extraAddons[extraAddonsLoaded]; $.cachedScript(url).done(self.loadExtraAddons).fail((xhr) => self.failedExtraAddonLoad(url, xhr)); } else { if (!self.waitForInterfaceChanger) { extraLoadFinished = true; Engine.setBlockSendNextInit(false); Engine.reCallInitQueue(); }; }; }; this.require = function(url) { if (!extraLoadFinished) extraAddons.push(url); else throw "Ładowanie już się zakończyło."; }; var extraAddons = []; this.failedAddonLoad = function(id, xhr) { var addon = addons.list[id]; //if (_l() == "pl") console.warn("Nie udało się załadować dodatku "+addon.namePL+" ["+this.getUrl(addon)+"] ("+xhr.status + " - "+xhr.statusText+")"); //else console.warn("Failed to load addon "+addon.nameEN+" ["+this.getUrl(addon)+"] ("+xhr.status + " - "+xhr.statusText+")"); addons.console.log("failed to load addon '"+(_l() == "pl" ? addon.namePL : addon.nameEN)+"' ("+xhr.status + " - "+xhr.statusText+")", 1); this.onAddonLoad(true); }; this.failedExtraAddonLoad = function(url, xhr) { //if (_l() == "pl") console.warn("Nie udało się załadować skryptu z require "+url); //else console.warn("Failed to load required script "+url); addons.console.log("failed to load script from "+url+" ("+xhr.status + " - "+xhr.statusText+")", 1); this.loadExtraAddons(true); }; this.getUrl = function(addon) { return _l() == "pl" ? addon.urlPL : addon.urlEN; }; })(); /* ADDON WINDOW MODULE the actual body of the custom addon panel */ this.customPanel = new (function() { const self = this; this.addedCustomAddons = false; this.content = null; this.template = null; addons.dictionary.addMany([ { id: "priwAddonPanel::label", txt: { "pl": "> Dodatki Priweejta <", "en": "> Priw8's addons <" } }, { id: "priwAddonPanel::title", txt: { "pl": "Dodatki Priweejta", "en": "Priw8's addons" } }, { id: "priwAddonPanel::enableAddon", txt: { "pl": "Włącz", "en": "Enable" } }, { id: "priwAddonPanel::disableAddon", txt: { "pl": "Wyłącz", "en": "Disable" } } ]) this.openCustomAddons = function() { const wnd = new addons.Window({ header: addons.dictionary.get("priwAddonPanel::title"), element: self.content }); wnd.setLabel(addons.dictionary.get("priwAddonPanel::title")); } this.addCustomAddonButton = function() { const $wnd = window.Engine.addonsPanel.wnd.$[0]; const $list = $wnd.querySelector(".addon-list"); const $header = document.createElement("div"); $header.classList.add("custom-addon-header"); $header.innerHTML = addons.dictionary.get("priwAddonPanel::label"); $header.addEventListener("click", this.openCustomAddons); $list.appendChild($header); }; this.initCss = function() { const css = ` .addon-author { font-size: 75%; text-align: right; color: #333333; } .custom-addon-header { text-align: center; border-bottom: 1px solid #4b4949; font-size: 125%; color: #777777; height: 70px; line-height: 70px; cursor: pointer; } .custom-addon-header:hover { color: #AAAAAA; } .priw-addon { display: flex; flex-direction: row; justify-content: space-between; align-items: center; margin-top: 5px; margin-bottom: 5px; width: 420px; padding: 5px; background: rgba(255, 255, 255, 0.2); border: 1px solid #80808080; } .priw-addon-name { font-weight: bold; } .priw-addon-desc { font-size: 90%; } `; $("").appendTo("head"); }; this.generateSingleAddon = function(data, id) { const $addon = this.template.cloneNode(true); $addon.querySelector(".priw-addon-name").innerHTML = _l() == "pl" ? data.namePL : data.nameEN; $addon.querySelector(".priw-addon-desc").innerHTML = _l() == "pl" ? data.descPL : data.descEN; const btt = new addons.Button({ label: addons.storage.getAddonState(id) ? addons.dictionary.get("priwAddonPanel::disableAddon") : addons.dictionary.get("priwAddonPanel::enableAddon"), clb: () => { addons.storage.toggleAddon(id); if (addons.storage.getAddonState(id)) { btt.setLabel(addons.dictionary.get("priwAddonPanel::disableAddon")); btt.$.classList.add("green"); } else { btt.setLabel(addons.dictionary.get("priwAddonPanel::enableAddon")); btt.$.classList.remove("green"); } } }); if (addons.storage.getAddonState(id)) btt.$.classList.add("green"); $addon.querySelector(".priw-addon-toggle").appendChild(btt.get$()); return $addon; } this.generateContent = function() { this.content = document.createElement("div"); for (let id in addons.list) { const addon = addons.list[id]; if ((_l() == "pl" && !addon.disablePL) || (_l() == "en" && !addon.disableEN)) { this.content.appendChild( this.generateSingleAddon(addon, id) ); } } }; this.initTemplate = function() { this.template = document.createElement("div"); this.template.classList.add("priw-addon"); this.template.innerHTML = `
` } this.initTemplate(); this.generateContent(); this.initCss(); addons.emmiter.on("game-load", () => { detourAfter(Engine.addonsPanel, "manageVisible", () => { if (!this.addedCustomAddons) { this.addCustomAddonButton(); this.addedCustomAddons = true; }; addons.emmiter.emit("addons-toggle"); }); }); })(); /* POPUP MENU kept for compatibility reasons... */ this.popupMenu = function(menu, e) { window.Engine.interface.showPopupMenu(menu, e); }; /* SETTINGS MODULE provides custom settings */ this.settings = new (function() { var self = this; var extraSettings = []; var $extraSettings, $scrollPane; addons.dictionary.add("addon_settings", { pl: "Ustawienia dodatków", en: "Addon settings" }) if (!Storage.get("addonsettings")) { Storage.set("addonsettings", {}); }; this.init = function() { var _old = Engine.settings.toggle; Engine.settings.toggle = function() { var ret = _old.apply(this, arguments); self.manageExtraSettings(); addons.emmiter.emit("settings-toggle"); return ret; }; }; this.updateSettingTable = function() { $extraSettings = document.createElement("div"); $extraSettings.classList.add("seccond-c"); var $header = document.createElement("h2"); $header.classList.add("settings-addons"); $header.innerHTML = ""+_txt("addon_settings")+""; $extraSettings.appendChild($header); var $list = document.createElement("ul"); $list.classList.add("hero-options"); var html = "", setting, enabled; for (var i=0; i"+setting.txt+""; }; $list.innerHTML = html; $list.addEventListener("click", this.toggleSetting); $extraSettings.appendChild($list); }; this.toggleSetting = function(e) { const path = e.composedPath(); // e.path tylko na chrome jest, upsik for (var i=0; i