FlatMMO plugin framework
Bu script direkt olarak kurulamaz. Başka scriptler için bir kütüphanedir ve meta yönergeleri içerir // @require https://update.greasyfork.org/scripts/544062/1878469/FlatMMOPlus.js
// ==UserScript==
// @name FlatMMOPlus
// @namespace com.dounford.flatmmo
// @version 1.5.1
// @description FlatMMO plugin framework
// @author Dounford adapted from Anwinity IPP
// @match *://flatmmo.com/play.php*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const VERSION = "1.5.1";
Set.prototype.some = function(predicate) {
for (const item of this) {
if (predicate(item)) {
return true;
}
}
return false;
};
Set.prototype.toggle = function(item) {
if (this.has(item)) {
this.delete(item)
} else {
this.add(item)
}
};
const LOCAL_STORAGE_KEY_DEBUG = "FlatMMOPlus:debug";
const CONFIG_TYPES_LABEL = ["label"];
const CONFIG_TYPES_PANEL = ["panel"];
const CONFIG_TYPES_BOOLEAN = ["boolean", "bool", "checkbox"];
const CONFIG_TYPES_INTEGER = ["integer", "int"];
const CONFIG_TYPES_FLOAT = ["number", "num", "float"];
const CONFIG_TYPES_RANGE = ["range"];
const CONFIG_TYPES_STRING = ["string", "text"];
const CONFIG_TYPES_SELECT = ["select"];
const CONFIG_TYPES_COLOR = ["color"];
const CONFIG_TYPES_LIST = ["list", "array"];
const CONFIG_TYPES_RELATION = ["relation", "key", "object"];
const CONFIG_TYPES_BUTTON = ["button", "btn"];
const CONFIG_TYPES_PRIORITY = ["sort", "priority"];
const CHAT_COMMAND_NO_OVERRIDE = ["help"];
const defaultHotkeys = [
//Default F keys
{
key: "F1",
name: "RUN!",
description: "Toggle Run",
category: "actions",
func: () => window.FlatMMOPlus.sendMessage("SHORTCUT_KEY=F1")
},
{
key: "F2",
name: "Eat",
description: "Consumes a piece of food",
category: "actions",
func: () => window.FlatMMOPlus.sendMessage("SHORTCUT_KEY=F2")
},
{
key: "F3",
name: "Fire",
description: "Lights a fire",
category: "actions",
func: () => window.FlatMMOPlus.sendMessage("SHORTCUT_KEY=F3")
},
{
key: "F4",
name: "Dodge",
description: "Activates dodge ability",
category: "actions",
func: () => window.FlatMMOPlus.sendMessage("SHORTCUT_KEY=F4")
},
//Equipment
{
key: "F6",
name: "Preset 1",
description: "Equips Preset 1",
category: "equipments",
func: () => window.FlatMMOPlus.sendMessage("SHORTCUT_KEY=F6")
},
{
key: "F7",
name: "Preset 2",
description: "Equips Preset 2",
category: "equipments",
func: () => window.FlatMMOPlus.sendMessage("SHORTCUT_KEY=F7")
},
{
key: "F8",
name: "Preset 3",
description: "Equips Preset 3",
category: "equipments",
func: () => window.FlatMMOPlus.sendMessage("SHORTCUT_KEY=F8")
},
//Badges
{
key: "F9",
name: "Badge 1",
description: "Activates Badge 1",
category: "badges",
func: () => window.FlatMMOPlus.sendMessage("SHORTCUT_KEY=F9")
},
{
key: "F10",
name: "Badge 2",
description: "Activates Badge 2",
category: "badges",
func: () => window.FlatMMOPlus.sendMessage("SHORTCUT_KEY=F10")
},
{
key: "F11",
name: "Badge 3",
description: "Activates Badge 3",
category: "badges",
func: () => window.FlatMMOPlus.sendMessage("SHORTCUT_KEY=F11")
},
{
key: "F12",
name: "Badge 4",
description: "Activates Badge 4",
category: "badges",
func: () => window.FlatMMOPlus.sendMessage("SHORTCUT_KEY=F12")
},
//Teleports
{
key: "N/A",
name: "Everbrook Teleport",
description: "Teleports to Everbrook",
category: "teleports",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=teleport_everbrook")
},
{
key: "N/A",
name: "Mystic Vale Teleport",
description: "Teleports to Mystic Vale",
category: "teleports",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=teleport_mysticvale")
},
{
key: "N/A",
name: "Omboko Teleport",
description: "Teleports to Omboko",
category: "teleports",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=teleport_omboko")
},
{
key: "N/A",
name: "Dock Haven Teleport",
description: "Teleports to Dock Haven",
category: "teleports",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=teleport_dock_haven")
},
{
key: "N/A",
name: "Jafa Teleport",
description: "Teleports to Jafa",
category: "teleports",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=teleport_jafa_outpost")
},
{
key: "N/A",
name: "Frostvale Teleport",
description: "Teleports to Frostvale",
category: "teleports",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=teleport_frostvale")
},
{
key: "N/A",
name: "Stuck",
description: "Teleports to /stuck",
category: "teleports",
func: () => window.FlatMMOPlus.sendMessage("CHAT=/stuck")
},
//Worship
{
key: "N/A",
name: "Remote Sell",
description: "Opens Sell Menu",
category: "worship",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=remote_sell")
},
{
key: "N/A",
name: "Dig",
description: "Dig",
category: "worship",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=dig")
},
{
key: "N/A",
name: "Timers",
description: "Opens Timers Menu",
category: "worship",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=timers")
},
{
key: "N/A",
name: "Hell Burrying",
description: "Burries your bones",
category: "worship",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=auto_hell_burying")
},
{
key: "N/A",
name: "Hunting Contact",
description: "Opens Hunting Perks Menu",
category: "worship",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=hunting_contact")
},
{
key: "N/A",
name: "Mass Pickup",
description: "Picks all your items",
category: "worship",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=mass_pickup")
},
{
key: "N/A",
name: "Focus",
description: "Increases Damage by 5 and Accuracy by 2",
category: "worship",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=focus")
},
{
key: "N/A",
name: "Clarity",
description: "Increases Damage by 15 and Accuracy by 5",
category: "worship",
func: () => window.FlatMMOPlus.sendMessage("USE_WORSHIP=clarity")
},
//Gamehotkeys
{
key: "Enter",
name: "Enter",
description: "Enter",
private: true,
func: () => enter_pressed()
},
{
key: "Backspace",
name: "Backspace",
description: "Backspace",
private: true,
repeat: true,
func: (e) => {
//It doesn't need to check the activeElement because it won't run on input/textarea
if(chat_ele.value.trim().length > 0) {
chat_ele.value = chat_ele.value.substring(0, chat_ele.value.length - 1);
}
if(chat_ele.value.trim().length == 0) {
request_unfocus_chatbox();
}
}
},
{
key: "Escape",
name: "Escape",
description: "Escape",
private: true,
func: (e) => {
let closed = false;
for (const s of opened_modals) {
close_modal(s);
closed = true;
}
if(!closed) {
close_bank();
close_global_market();
}
}
},
//Scrolls
{
key: "N/A",
name: "Chef's House Scroll",
description: "Uses a scroll to teleport to Chef's House",
category: "Teleport Book",
func: () => {
window.FlatMMOPlus.sendMessage(`TELE_BOOK=chefs_house_teleport_scroll`);
}
},
{
key: "N/A",
name: "Thieves Hideout Scroll",
description: "Uses a scroll to teleport to Thieves Hideout",
category: "Teleport Book",
func: () => {
window.FlatMMOPlus.sendMessage(`TELE_BOOK=thieves_hideout_teleport_scroll`);
}
},
{
key: "N/A",
name: "Greenhouse Scroll",
description: "Uses a scroll to teleport to Greenhouse",
category: "Teleport Book",
func: () => {
window.FlatMMOPlus.sendMessage(`TELE_BOOK=greenhouse_teleport_scroll`);
}
},
{
key: "N/A",
name: "Rogue's Grave Scroll",
description: "Uses a scroll to teleport to Rogue's Grave",
category: "Teleport Book",
func: () => {
window.FlatMMOPlus.sendMessage(`TELE_BOOK=rogue_teleport_scroll`);
}
},
{
key: "N/A",
name: "Phantos Mansion Scroll",
description: "Uses a scroll to teleport to Phantos Mansion",
category: "Teleport Book",
func: () => {
window.FlatMMOPlus.sendMessage(`TELE_BOOK=phantos_teleport_scroll`);
}
},
//Menus
{
key: "N/A",
name: "View Bank",
description: "Opens Bank Panel",
category: "panels",
func: () => {
window.FlatMMOPlus.sendMessage(`VIEW_BANK`);
}
},
{
key: "N/A",
name: "View Hunting",
description: "Opens Hunting Panel",
category: "panels",
func: () => {
switch_panels('hunting');
}
}
]
let hotkeyOverride = {};
//This is used for hot updates
let plugins = {};
let panels = {};
let nextUniqueId = 1;
let customChatCommands = {
help: (command, data) => {
console.log("help", command, data);
}
};
let customChatHelp = {};
let currentPanel = "inventory";
let currentSettingsPanel = "sound";
let loggedIn = false;
let isFighting = false;
let original_onmessage;
let original_sendmessage;
let original_switch_panels;
let original_settings_modal_tab;
let flatnotifications = {};
const pageWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
if (pageWindow.FlatMMOPlus) {
if(pageWindow.FlatMMOPlus.version >= VERSION) {
if (window !== pageWindow) {
window.AnimationSheetPlus = pageWindow.AnimationSheetPlus;
window.FlatMMOPlusPlugin = pageWindow.FlatMMOPlusPlugin;
window.FlatMMOPlus = pageWindow.FlatMMOPlus;
}
return;
}
plugins = pageWindow.FlatMMOPlus.plugins;
panels = pageWindow.FlatMMOPlus.panels;
nextUniqueId = pageWindow.FlatMMOPlus.nextUniqueId;
customChatCommands = pageWindow.FlatMMOPlus.customChatCommands;
customChatHelp = pageWindow.FlatMMOPlus.customChatHelp;
currentPanel = pageWindow.FlatMMOPlus.currentPanel;
currentSettingsPanel = pageWindow.FlatMMOPlus.currentSettingsPanel;
loggedIn = pageWindow.FlatMMOPlus.loggedIn;
isFighting = pageWindow.FlatMMOPlus.isFighting;
original_onmessage = pageWindow.FlatMMOPlus.original_onmessage || Globals.websocket.onmessage;
original_sendmessage = pageWindow.FlatMMOPlus.original_sendmessage || Globals.websocket.send;
original_switch_panels = pageWindow.FlatMMOPlus.original_switch_panels || window.switch_panels;
original_settings_modal_tab = pageWindow.FlatMMOPlus.original_settings_modal_tab || window.settings_modal_tab;
flatnotifications = pageWindow.FlatMMOPlus.notifications || {};
if (pageWindow.FlatMMOPlus.fmpKeyDown) {
pageWindow.removeEventListener("keydown", pageWindow.FlatMMOPlus.fmpKeyDown, false);
}
document.getElementById("ui-panel-flatmmoplus")?.remove();
document.getElementById("settings-modal-plugins-panel-btn")?.remove();
document.getElementById("settings-modal-plugins-panel")?.remove();
document.querySelector(".settings-ui tbody tr:last-child")?.remove()
} else {
document.head.insertAdjacentHTML("beforeend", `<style>
.displaynone {
display: none !important;
}
#ui-panel-hotkeys-content {
h1 {
margin: 0;
text-transform: capitalize;
}
h3 {
margin-bottom: 0;
}
button {
cursor: pointer;
}
}
.fmpHotkeysConflict {
background-color: red;
color: white;
}
.fmpHotkeysCategory {
display: grid;
grid-template-columns: auto auto auto;
}
.fmp-priority-div {
list-style: none;
padding: 0;
}
.fmp-priority-item {
background-color: #fff;
cursor: grab;
}
.fmp-priority-item.dragging {
opacity: 0.5;
background-color: #e0e0e0;
}
#ui-panel-flatmmoplus {
height: 550px;
overflow-y: scroll;
scrollbar-width: thin;
text-align: justify;
textarea {
width: -webkit-fill-available;
width: -moz-available;
width: stretch;
resize: none;
}
input[type=checkbox] {
-webkit-appearance: none;
appearance: none;
position: relative;
width: 20px;
height: 10px;
border-radius: 15px;
background-color: #ccc;
outline: none;
cursor: pointer;
transition: background-color 0.3s;
top: 0.5rem
}
input[type=checkbox]::before {
content: '';
position: absolute;
top: 1px;
left: 1px;
width: 8px;
height: 8px;
background-color: #fff;
border-radius: 50%;
transition: transform 0.3s;
}
input[type=checkbox]:checked {
background-color: #4CAF50;
}
input[type=checkbox]:checked::before {
transform: translateX(10px);
}
label {
font-size: 1.17em;
font-weight: bold;
}
}
.flatmmoplus-plugin-config-section >div {
margin: 5px 0;
}
.fmp-list-div {
max-height: calc(5em + 60px);
overflow-y: auto;
text-align: center;
}
.fmp-list-item {
display: flex;
justify-content: space-between;
align-items: center;
div {
width: 100%;
padding: 5px;
}
}
.fmp-list-item:nth-child(odd) {
background-color: rgba(0, 0, 0, 0.3);
}
.fmp-list-close {
cursor: pointer;
font-size: 2rem;
}
.fmp-objectLabel {
display: grid;
grid-template-columns: auto auto;
justify-items: center;
font-weight: bold;
}
</style>`);
//The new hotkey system does everything these does
window.removeEventListener("keypress", keypress_listener, false);
window.removeEventListener("keyup", keyup_listener, false);
window.removeEventListener("keydown", keydown_listener, false);
document.getElementById("chat-text-input").onkeypress = null
original_onmessage = Globals.websocket.onmessage;
original_sendmessage = Globals.websocket.send;
original_switch_panels = window.switch_panels;
original_settings_modal_tab = window.settings_modal_tab;
//Record hotkey remap
window.addEventListener("click", (e) => {
const btn = e.target.closest("[data-hotkeystring]");
if(btn) {
window.FlatMMOPlus.handler.hotkeyElement = btn;
btn.innerText = "Recording";
}
})
}
function logFancy(s, color="#00f7ff") {
console.log("%cFlatMMOPlus: %c"+s, `color: ${color}; font-weight: bold; font-size: 12pt;`, "color: silver; font-weight: normal; font-size: 10pt;");
}
class AnimationSheetPlus {
constructor(filename, frames, speed, images) {
this.filename = filename;
this.running = false;
this.frame_at = 0;
this.FRAMES = frames;
this.SPEED = speed;
this.speed_at = 0;
this.animation_tick_at = animation_tick;
this.images = []
for(var i = 1; i <= this.FRAMES; i++) {
let image = new Image();
image.src = images[i - 1]
this.images.push(image);
}
}
get_frame() {
if(this.FRAMES > 0) {
if(this.SPEED == this.speed_at) {
//swtich frames
if(this.animation_tick_at != animation_tick) {
this.frame_at++;
this.animation_tick_at = animation_tick;
}
if(this.FRAMES == this.frame_at) {
this.frame_at = 0;
}
this.speed_at = 0;
} else {
this.speed_at++;
}
return this.images[this.frame_at];
} else {
return this.images[0];
}
}
}
class FlatMMOPlusPlugin {
constructor(id, opts) {
if(typeof id !== "string") {
throw new TypeError("FlatMMOPlusPlugin constructor takes the following arguments: (id:string, opts?:object)");
}
this.id = id;
this.opts = opts || {};
this.config = null;
this.changedConfigs = new Set();
}
getConfig(name) {
if(!this.config) {
FlatMMOPlus.loadPluginConfigs(this.id);
}
if(this.config) {
return this.config[name];
}
}
/*
onConfigsChanged() { }
onLogin() { }
onMessageReceived(data) { }
onChat(data) { }
onPanelChanged(panelBefore, panelAfter)
onMapChanged(mapBefore, mapAfter) { }
onInventoryChanged(inventoryBefore, inventoryAfter) { }
*/
}
class handlerPlugin extends FlatMMOPlusPlugin {
constructor() {
super("handler", {
about: {
name: "Plugin Handler",
version: VERSION,
author: "Liam",
description: "FlatMMO+ Configs"
},
config: [
{
id: "globalSettings",
label: "Share settings across all profiles",
type: "boolean",
default: false
},
{
id: "turnSettingsGlobal",
label: "Copy this profile's options to all profiles",
text: "Copy",
type: "button",
func: "FlatMMOPlus.handler.shareThisSettings",
//class: "btnClass"
},
{
id: "hoverNotifications",
label: "Plugin Notifications Orbs",
type: "boolean",
default: false
},
{
id: "hoverNotificationHeight",
label: "Notification Orbs Height",
type: "range",
min: 2,
max: 14,
step: 1,
default: 10,
},
{
id: "hoverNotificationAlpha",
label: "Notification Orbs Transparency",
type: "range",
min: 0,
max: 100,
default: 100
}
]
})
this.chatInput = document.getElementById("chat-text-input") || null;
this.hotkeyCategories = new Set();
this.hotkeys = {};
this.hotkeyElement = null;
window.addEventListener("keydown", e => {
})
}
shareThisSettings() {
let keys = {
main: Globals.local_username,
scripts: new Set(),
alts: new Set(),
}
for (const key in localStorage) {
const match = key.match(/flatmmoplus\.(.*?)(?:\.(.*?))?\.config/);
if(match === null) continue;
const id = match[1];
keys.scripts.add(id);
const username = match[2];
if(username && username !== Globals.local_username) {
keys.alts.add(username);
}
}
keys.scripts.forEach(script => {
const value = localStorage.getItem(`flatmmoplus.${script}.${keys.main}.config`) || '{}';
keys.alts.forEach(profile => {
localStorage.setItem(`flatmmoplus.${script}.${profile}.config`)
})
})
}
}
class FlatMMOPlus {
constructor() {
this.version = VERSION;
this.plugins = plugins;
this.panels = panels;
this.debug = false;
this.nextUniqueId = nextUniqueId;
this.customChatCommands = customChatCommands
this.customChatHelp = customChatHelp;
this.currentPanel = currentPanel;
this.currentSettingsPanel = currentSettingsPanel;
this.loggedIn = loggedIn;
this.isFighting = isFighting;
this.in_combat_ticker = 0;
this.currentAction = this.currentAction;
this.original_onmessage = original_onmessage;
this.original_sendmessage = original_sendmessage;
this.original_switch_panels = original_switch_panels;
this.original_settings_modal_tab = original_settings_modal_tab;
this.notifications = flatnotifications;
this.level = [
0,0,108,177,265,380,527,717,956,1254,1622,2068,2605,3245,4000,4885,5913,7100,8464,10022,11794,13799,16060,18599,21442,24614,28145,32063,36400,41191,46470,52277,58652,65637,73279,81625,90728,100642,111424,123135,135841,149611,164516,180634,198047,216840,237105,258938,282440,307721,334893,364077,395399,428994,465003,503577,544871,589054,636300,686794,740732,798320,859775,925326,995213,1069691,1149027,1233503,1323417,1419081,1520824,1628993,1743952,1866086,1995798,2133515,2279683,2434772,2599278,2773721,2958649,3154637,3362289,3582243,3815166,4061762,4322767,4598959,4891153,5200203,5527011,5872521,6237725,6623665,7031436,7462185,7917120,8397507,8904674,9440017,10004999,10601158,11230106,11893534,12593217,13331018,14108890,14928883,15793144,16703929,17663602,18674641,19739647,20861344,22042590,23286382,24595860,25974317,27425202,28952134,30558903,32249481,34028033,35898920,37866713,39936202,42112405,44400579,46806231,49335129,51993317,54787124,57723179,60808425,64050133,67455918,71033752,74791986,78739361,82885031,87238578,91810037,96609909,101649191,106939392,112492559,118321304,124438823,130858933,137596089,144665421,152082764,159864685,168028523,176592418,185575354,194997190,204878707,215241643,226108743,237503800,249451703,261978489,275111393,288878903,303310817,318438300,334293948,350911852,368327667,386578678,405703877,425744040,446741808,468741765,491790534,515936862,541231719,567728396,595482609,624552610,654999299,686886341,720280294,755250734,791870395,830215305,870364937,912402361,956414404,1002491823,1050729473,1101226494,1154086502,1209417785,1267333518,1327951974,1391396753,1457797021,1527287754,1600009999
];
if(localStorage.getItem(LOCAL_STORAGE_KEY_DEBUG) == "1") {
this.debug = true;
}
}
}
//I'm not sure why Smitty has both keydown and the deprecated keypress, I will merge both codes here
FlatMMOPlus.prototype.fmpKeyDown = function(e) {
//This should make sure inputs don't break
if(document.activeElement.nodeName === "INPUT" || document.activeElement.nodeName === "TEXTAREA" || document.activeElement.getAttribute("contenteditable") === "true") {
if(document.activeElement.id === "chat-text-input" && e.key === "Enter") {
enter_pressed()
}
return;
};
if(has_npc_chat_message_modal_open() && e.key === " ") {
document.getElementById("npc-chat-message-modal-continue-btn").click();
e.preventDefault();
return;
}
//This doesn't need a switch case
if(has_npc_chat_options_modal_open() && e.keyCode > 48 && e.keyCode < 58) {
//1 is 49
const value = e.keyCode - 49;
let wrapper = document.getElementById("npc-chat-options-modal-content");
let options = wrapper.getElementsByTagName("div");
if(options[value] && options[value].style.display != 'none') {
options[value].click();
}
return;
}
//Modifiers can't have hotkeys on them
if(e.key === "Shift" || e.key === "Control" || e.key === "Alt" || e.key === "Meta") return;
const stringKey = window.FlatMMOPlus.formatHotkey(e);
if(window.FlatMMOPlus.handler.hotkeyElement !== null) {
e.preventDefault();
window.FlatMMOPlus.remapHotkey(e, stringKey);
return;
}
if(window.FlatMMOPlus.handler.hotkeys.hasOwnProperty(stringKey)) {
e.preventDefault();
window.FlatMMOPlus.handler.hotkeys[stringKey].forEach(h =>{
//Most hotkeys won't allow holding down to spam them
if(h.repeat || e.repeat == false) {
h.func(e);
}
});
return;
}
if(has_modal_open()) return;
if(document.activeElement.id != "body") {return;}
//Vanilla chat has a 100 characters limitation
if(LOCAL_CHAT_MAX_LENGTH <= chat_ele.value.length && this.handler.chatInput?.id === "chat-text-input") {
return;
}
//Special keys won't be added
if(e.key.length > 1) return;
//Using key instead of code should fix any browser/keyboard issues
chat_ele.value += e.key;
}
FlatMMOPlus.prototype.remapHotkey = function(e, stringKey) {
if(e.key === "Escape") {
stringKey = "N/A"
}
const hotkeyName = this.handler.hotkeyElement?.getAttribute("data-hotkeyname");
const hotkeyString = this.handler.hotkeyElement?.getAttribute("data-hotkeystring");
const hotkeyIndex = this.handler.hotkeys[hotkeyString]?.findIndex(h => h.name === hotkeyName)
if(hotkeyIndex !== -1) {
const hotkey = this.handler.hotkeys[hotkeyString].splice(hotkeyIndex, 1);
if(this.handler.hotkeys[hotkeyString].length === 0) {
delete this.handler.hotkeys[hotkeyString];
}
if(!this.handler.hotkeys.hasOwnProperty(stringKey)) {
this.handler.hotkeys[stringKey] = [];
}
this.handler.hotkeys[stringKey].push(...hotkey);
this.handler.hotkeyElement.innerText = stringKey;
this.handler.hotkeyElement.setAttribute("data-hotkeystring", stringKey);
hotkeyOverride[hotkeyName] = stringKey;
localStorage.setItem("FMP-hotkeys", JSON.stringify(hotkeyOverride));
this.checkHotkeyConflicts();
}
this.handler.hotkeyElement = null;
}
FlatMMOPlus.prototype.registerHotkeyCategory = function(category) {
const id = "fmp-hotkeys-category-" + category
if(document.getElementById(id)) return;
const container = document.createElement("div");
container.innerHTML = `<h1>${category}</h1>
<div class="fmpHotkeysCategory" id="${id}"></div>`
document.getElementById("ui-panel-hotkeys-content").append(container);
this.handler.hotkeyCategories.add(category);
}
//Hotkeys can be overwriten
FlatMMOPlus.prototype.registerHotkey = function(hotkey) {
if(typeof hotkey.func !== "function") {
console.error("You forgot the the hotkey.func or it is not a function")
}
//Modifiers are optional
hotkey.ctrlKey = hotkey.ctrlKey || false;
hotkey.altKey = hotkey.altKey || false;
hotkey.shiftKey = hotkey.shiftKey || false;
hotkey.metaKey = hotkey.metaKey || false;
let stringKey = this.formatHotkey(hotkey);
//Users can modify the key
if(hotkeyOverride.hasOwnProperty(hotkey.name)) {
stringKey = hotkeyOverride[hotkey.name];
}
//Plugins can override or redeclare keyboards
const oldEl = document.getElementById("fmp-hotkeysContainer-" + hotkey.name);
if(oldEl) {
oldEl.remove()
}
//Some hotkeys should not show on hotkey menu
if(!hotkey.private) {
//Hotkey Panel has categories, if not specified it will be misc, if it doesn't exist it will add
hotkey.category = hotkey.category || "misc";
if(!this.handler.hotkeyCategories.has(hotkey.category)) {
this.registerHotkeyCategory(hotkey.category)
}
const categoryId = "fmp-hotkeys-category-" + hotkey.category;
document.getElementById(categoryId).insertAdjacentHTML("beforeend", `<div id="fmp-hotkeysContainer-${hotkey.name}">
<h3>${hotkey.name}</h3>
<p>${hotkey.description}</p>
<button class="fmp-hotkeys-buttons" id="fmp-hotkeys-${hotkey.name}" data-hotkeyname="${hotkey.name}" data-hotkeystring="${stringKey}">${stringKey}</button>
</div>`)
}
//Conflicts are fine
if(!this.handler.hotkeys.hasOwnProperty(stringKey)) {
this.handler.hotkeys[stringKey] = [];
}
this.handler.hotkeys[stringKey].push(hotkey);
this.checkHotkeyConflicts();
}
//Some plugins may remove default hotkeys for some reason
FlatMMOPlus.prototype.deleteHotkey = function(hotkeyName) {
const el = document.getElementById("fmp-hotkeysContainer-" + hotkeyName);
if(el) {
el.remove()
}
//Remove it from hotkey map
this.handler.hotkeys = Object.fromEntries(
Object.entries(this.handler.hotkeys).map(([key, array]) => [
key,
array.filter(h => h.name !== hotkeyName)
])
)
this.checkHotkeyConflicts();
}
FlatMMOPlus.prototype.formatHotkey = function(key) {
const parts = [];
if (key.ctrlKey) parts.push("Ctrl");
if (key.altKey) parts.push("Alt");
if (key.shiftKey) parts.push("Shift");
if (key.metaKey) parts.push("Meta");
parts.push(key.key.toUpperCase());
return parts.join(" + ");
};
FlatMMOPlus.prototype.checkHotkeyConflicts = function() {
for (const list of Object.values(this.handler.hotkeys)) {
const hasConflict = list.length > 1;
list.forEach(h => {
const el = document.getElementById(`fmp-hotkeys-${h.name}`);
if (el) {
el.classList.toggle("fmpHotkeysConflict", hasConflict);
}
});
}
}
FlatMMOPlus.prototype.registerCustomChatCommand = function(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("FlatMMOPlus.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(`FlatMMOPlus: 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];
}
}
FlatMMOPlus.prototype.handleCustomChatCommand = function(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;
}
FlatMMOPlus.prototype.uniqueId = function() {
return this.nextUniqueId++;
}
FlatMMOPlus.prototype.setDebug = function(debug) {
if(debug) {
this.debug = true;
localStorage.setItem(LOCAL_STORAGE_KEY_DEBUG, "1");
}
else {
this.debug = false;
localStorage.removeItem(LOCAL_STORAGE_KEY_DEBUG);
}
}
FlatMMOPlus.prototype.setPluginConfigUIDirty = function(id, dirty, configId) {
if(typeof id !== "string" || typeof dirty !== "boolean") {
throw new TypeError("FlatMMOPlus.setPluginConfigUIDirty takes the following arguments: (id:string, dirty:boolean)");
}
const plugin = this.plugins[id];
if(configId) {
plugin.changedConfigs.add(configId);
}
const button = document.getElementById(`flatmmoplus-configbutton-${plugin.id}-apply`);
if(button) {
button.disabled = !dirty;
}
}
FlatMMOPlus.prototype.newListField = function(pluginId, configId, text = "New Field") {
if(typeof pluginId !== "string" || typeof configId !== "string" || typeof text !== "string") {
throw new TypeError("FlatMMOPlus.newListField takes the following arguments: (id:string, text:string)");
}
const plugin = this.plugins[pluginId];
const parentDiv = document.getElementById(`flatmmoplus-config-${pluginId}-${configId}`);
const item = document.createElement("div");
const textDiv = document.createElement("div");
const closeSpan = document.createElement("span");
item.className = "fmp-list-item";
item.addEventListener("dblclick", function(e){
e.preventDefault();
textDiv.contentEditable = true;
textDiv.focus();
})
item.addEventListener("contextmenu", function(e) {
e.preventDefault();
textDiv.contentEditable = true;
textDiv.focus();
});
textDiv.innerText = text;
textDiv.spellcheck = false;
textDiv.autocorrect = false;
textDiv.addEventListener("focusout", function(e) {
this.contentEditable = false;
plugin.changedConfigs.add(configId);
window.FlatMMOPlus.setPluginConfigUIDirty(pluginId, true, configId)
});
closeSpan.innerText = "×";
closeSpan.className = "fmp-list-close";
closeSpan.addEventListener("click", function(){
item.remove();
window.FlatMMOPlus.setPluginConfigUIDirty(pluginId, true, configId)
})
item.appendChild(textDiv);
item.appendChild(closeSpan);
parentDiv.appendChild(item);
parentDiv.scrollTop = parentDiv.scrollHeight;
plugin.changedConfigs.add(configId);
window.FlatMMOPlus.setPluginConfigUIDirty(pluginId, true, configId)
if(text = "New Field") {
textDiv.contentEditable = true;
textDiv.focus();
}
}
FlatMMOPlus.prototype.newObjectField = function(pluginId, configId, key = "Key", value = "Value") {
if(typeof pluginId !== "string" || typeof configId !== "string" || typeof key !== "string") {
throw new TypeError("FlatMMOPlus.newObjectField takes the following arguments: (id:string, key:string, value:string)");
}
const plugin = this.plugins[pluginId];
const parentDiv = document.getElementById(`flatmmoplus-config-${pluginId}-${configId}`);
const item = document.createElement("div");
const keyDiv = document.createElement("div");
const valueDiv = document.createElement("div");
const closeSpan = document.createElement("span");
item.className = "fmp-list-item";
function editField(e) {
e.preventDefault();
const el = e.target;
el.contentEditable = true;
el.focus();
}
keyDiv.ondblclick = (e) => editField(e);
keyDiv.oncontextmenu = (e) => editField(e);
valueDiv.ondblclick = (e) => editField(e);
valueDiv.oncontextmenu = (e) => editField(e);
keyDiv.innerText = key;
keyDiv.spellcheck = false;
keyDiv.autocorrect = false;
valueDiv.innerText = value;
valueDiv.spellcheck = false;
valueDiv.autocorrect = false;
function focusOut(e) {
e.preventDefault();
const el = e.target;
el.contentEditable = false;
plugin.changedConfigs.add(configId);
window.FlatMMOPlus.setPluginConfigUIDirty(pluginId, true, configId)
}
keyDiv.onblur = (e) => focusOut(e);
valueDiv.onblur = (e) => focusOut(e);
closeSpan.innerText = "×";
closeSpan.className = "fmp-list-close";
closeSpan.addEventListener("click", function(){
item.remove();
window.FlatMMOPlus.setPluginConfigUIDirty(pluginId, true, configId)
})
item.appendChild(keyDiv);
item.appendChild(valueDiv);
item.appendChild(closeSpan);
parentDiv.appendChild(item);
parentDiv.scrollTop = parentDiv.scrollHeight;
plugin.changedConfigs.add(configId);
window.FlatMMOPlus.setPluginConfigUIDirty(pluginId, true, configId)
if(key = "Key") {
keyDiv.contentEditable = true;
keyDiv.focus();
}
}
FlatMMOPlus.prototype.newPriorityField = function(pluginId, configId, value = "New Item") {
if(typeof pluginId !== "string" || typeof configId !== "string" || typeof value !== "string") {
throw new TypeError("FlatMMOPlus.newPriorityField takes the following arguments: (pluginId: string, configId:string, value: string)");
}
const plugin = this.plugins[pluginId];
const config = plugin.opts.config.find(c => c.id == configId);
const parentDiv = document.getElementById(`flatmmoplus-config-${pluginId}-${configId}`);
const item = document.createElement("div");
const textDiv = document.createElement("div");
item.className = "fmp-priority-item fmp-list-item";
item.setAttribute("draggable", true);
textDiv.innerText = value;
textDiv.spellcheck = false;
textDiv.autocorrect = false;
item.appendChild(textDiv);
parentDiv.appendChild(item);
//Some lists may let users add/remove/edit items
if(config.canAddNew) {
const closeSpan = document.createElement("span");
closeSpan.innerText = "×";
closeSpan.className = "fmp-list-close";
closeSpan.addEventListener("click", function(){
item.remove();
window.FlatMMOPlus.setPluginConfigUIDirty(pluginId, true, configId)
})
item.appendChild(closeSpan);
item.addEventListener("contextmenu", function(e) {
e.preventDefault();
textDiv.contentEditable = true;
textDiv.focus();
});
textDiv.addEventListener("focusout", function(e) {
this.contentEditable = false;
plugin.changedConfigs.add(configId);
window.FlatMMOPlus.setPluginConfigUIDirty(pluginId, true, configId)
});
if(value = "New Item") {
textDiv.contentEditable = true;
textDiv.focus();
}
}
parentDiv.scrollTop = parentDiv.scrollHeight;
plugin.changedConfigs.add(configId);
window.FlatMMOPlus.setPluginConfigUIDirty(pluginId, true, configId)
}
FlatMMOPlus.prototype.loadPluginConfigs = function(id) {
if (typeof id !== "string") {
throw new TypeError("FlatMMOPlus.reloadPluginConfigs takes the following arguments: (id:string)");
}
let value;
const globalValue = localStorage.getItem(`flatmmoplus.${id}.config`) || "{}";
const accountValue = localStorage.getItem(`flatmmoplus.${id}.${Globals.local_username}.config`);
if(id !== "handler") {
const usesGlobal = this.handler.config.globalSettings;
if(usesGlobal) {
value = globalValue;
} else if (accountValue == null) {
value = globalValue;
//Make sure it exists next time
localStorage.setItem(`flatmmoplus.${id}.${Globals.local_username}.config`, globalValue);
} else {
value = accountValue;
}
} else {
value = globalValue;
}
const plugin = this.plugins[id];
const config = {};
let stored;
try {
stored = JSON.parse(value || "{}");
} 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 = document.getElementById(`flatmmoplus-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.checked = value;
} else if (CONFIG_TYPES_INTEGER.includes(cfg.type) && typeof value === "number") {
el.value = value;
} else if (CONFIG_TYPES_FLOAT.includes(cfg.type) && typeof value === "number") {
el.value = value;
} else if (CONFIG_TYPES_RANGE.includes(cfg.type) && typeof value === "number") {
el.value = value;
document.getElementById(`flatmmoplus-config-${plugin.id}-${cfg.id}-value`).innerText = value
} else if (CONFIG_TYPES_STRING.includes(cfg.type) && typeof value === "string") {
el.value = value;
} else if (CONFIG_TYPES_SELECT.includes(cfg.type) && typeof value === "string") {
el.value = value;
} else if (CONFIG_TYPES_COLOR.includes(cfg.type) && typeof value === "string") {
el.value = value;
} else if (CONFIG_TYPES_LIST.includes(cfg.type)) {
el.innerHTML = "";
if(typeof value === "string") {
value = value.split(",")
.map(item => item.trim())
.filter(item=> item !== "");
if(cfg.unique) {
const valuesSet = new Set(value);
value = Array.from(valuesSet);
}
config[cfg.id] = value;
}
value.forEach(item => {
window.FlatMMOPlus.newListField(id, cfg.id, item);
})
} else if (CONFIG_TYPES_PRIORITY.includes(cfg.type)) {
el.innerHTML = "";
if (cfg.unique) {
const valuesSet = new Set(value);
value = Array.from(valuesSet);
}
console.log(value)
value.forEach(item => {
window.FlatMMOPlus.newPriorityField(id, cfg.id, item);
})
} else if (CONFIG_TYPES_RELATION.includes(cfg.type)) {
el.innerHTML = "";
for(let item in value) {
window.FlatMMOPlus.newObjectField(id, cfg.id, item, value[item]);
}
}
if(cfg.onChange) {
el.onchange = cfg.onChange;
}
}
});
}
plugin.config = config;
plugin.changedConfigs.clear();
this.setPluginConfigUIDirty(id, false);
if (typeof plugin.onConfigsChanged === "function") {
plugin.onConfigsChanged();
}
}
FlatMMOPlus.prototype.savePluginConfigs = function(id) {
if (typeof id !== "string") {
throw new TypeError("FlatMMOPlus.savePluginConfigs takes the following arguments: (id:string)");
}
let key;
if(id !== "handler") {
key = this.handler.config.globalSettings ? `flatmmoplus.${id}.config` : `flatmmoplus.${id}.${Globals.local_username}.config`;
} else {
key = `flatmmoplus.${id}.config`;
}
const plugin = this.plugins[id];
const config = {};
if (plugin.opts.config && Array.isArray(plugin.opts.config)) {
plugin.opts.config.forEach(cfg => {
const el = document.getElementById(`flatmmoplus-config-${plugin.id}-${cfg.id}`);
if (CONFIG_TYPES_BOOLEAN.includes(cfg.type)) {
config[cfg.id] = el.checked;
} else if (CONFIG_TYPES_INTEGER.includes(cfg.type)) {
config[cfg.id] = parseInt(el.value);
} else if (CONFIG_TYPES_FLOAT.includes(cfg.type)) {
config[cfg.id] = parseFloat(el.value);
} else if (CONFIG_TYPES_RANGE.includes(cfg.type)) {
config[cfg.id] = parseInt(el.value);
} else if (CONFIG_TYPES_STRING.includes(cfg.type)) {
config[cfg.id] = el.value;
} else if (CONFIG_TYPES_SELECT.includes(cfg.type)) {
config[cfg.id] = el.value;
} else if (CONFIG_TYPES_COLOR.includes(cfg.type)) {
config[cfg.id] = el.value;
} else if (CONFIG_TYPES_LIST.includes(cfg.type)) {
let values = [];
document.querySelectorAll(`#flatmmoplus-config-${plugin.id}-${cfg.id} div div`).forEach(el => {
values.push(el.innerText)
})
//This will remove duplicates
if(cfg.unique) {
const valuesSet = new Set(values);
values = Array.from(valuesSet);
}
config[cfg.id] = values;
} else if (CONFIG_TYPES_PRIORITY.includes(cfg.type)) {
let values = [];
document.querySelectorAll(`#flatmmoplus-config-${plugin.id}-${cfg.id} div div`).forEach(el => {
values.push(el.innerText)
})
//This will remove duplicates
if(cfg.unique) {
const valuesSet = new Set(values);
values = Array.from(valuesSet);
}
config[cfg.id] = values;
} else if (CONFIG_TYPES_RELATION.includes(cfg.type)) {
const values = {};
document.querySelectorAll(`#flatmmoplus-config-${plugin.id}-${cfg.id}>div`).forEach(el => {
values[el.children[0].innerText] = el.children[1].innerText;
})
config[cfg.id] = values;
}
});
}
plugin.config = config;
localStorage.setItem(key, JSON.stringify(config));
this.setPluginConfigUIDirty(id, false);
if (typeof plugin.onConfigsChanged === "function") {
plugin.onConfigsChanged();
}
plugin.changedConfigs.clear();
}
FlatMMOPlus.prototype.showWarning = function(message, color = "orange") {
add_to_chat("none", "none", "none", color, message);
}
FlatMMOPlus.prototype.addPanel = function(id, title, content, hasTabButton) {
if(typeof id !== "string" || typeof title !== "string" || (typeof content !== "string" && typeof content !== "function") ) {
throw new TypeError("FlatMMOPlus.addPanel takes the following arguments: (id:string, title:string, content:string|function)");
}
if(this.panels[id]) {
console.error("There already is a panel with this id");
return;
}
if(hasTabButton) {
const firstPanel = document.querySelector("#settings-modal-sound-panel");
firstPanel.insertAdjacentHTML("beforebegin", `<div class="settings-modal-panel-btn hover" onclick="settings_modal_tab('${id}')" id="settings-modal-${id}-panel-btn" style="margin-right: 10px;">${title}</div>`)
}
const lastPanel = document.querySelector("#settings-modal-mutes-panel");
lastPanel.insertAdjacentHTML("afterend",`
<div class="settings-modal-panel" style="display: none" id="settings-modal-${id}-panel">
<div id="ui-panel-${id}-content"></div>
</div>
`);
this.panels[id] = {
id: id,
title: title,
content: content
};
this.refreshPanel(id);
}
FlatMMOPlus.prototype.refreshPanel = function(id) {
if(typeof id !== "string") {
throw new TypeError("FlatMMOPlus.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 = document.getElementById(`ui-panel-${id}-content`);
panelContent.innerHTML = content;
if(id === "plugins") {
this.forEachPlugin(plugin => {
this.loadPluginConfigs(plugin.id);
});
}
}
FlatMMOPlus.prototype.registerPlugin = function(plugin) {
if(!(plugin instanceof FlatMMOPlusPlugin)) {
throw new TypeError("FlatMMOPlus.registerPlugin takes the following arguments: (plugin:FlatMMOPlusPlugin)");
}
if(plugin.id in this.plugins) {
throw new Error(`FlatMMOPlusPlugin 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}`);
//Calls onlogin when the plugin is loaded with delay
if(this.loggedIn) {
plugin.onLogin();
}
}
FlatMMOPlus.prototype.forEachPlugin = function(f) {
if(typeof f !== "function") {
throw new TypeError("FlatMMOPlus.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);
}
});
}
FlatMMOPlus.prototype.setPanel = function(panel) {
if(typeof panel !== "string") {
throw new TypeError("FlatMMOPlus.setPanel takes the following arguments: (panel:string)");
}
window.settings_modal_tab(panel);
}
FlatMMOPlus.prototype.sendMessage = function(message) {
if(typeof message !== "string") {
throw new TypeError("FlatMMOPlus.sendMessage takes the following arguments: (message:string)");
}
if(Globals.websocket && Globals.websocket.readyState == 1) {
Globals.websocket.send(message);
}
}
FlatMMOPlus.prototype.hideCustomPanels = function() {
Object.values(this.panels).forEach((panel) => {
const el = document.getElementById(`settings-modal-${panel.id}-panel`);
if(el) {
el.style.display = "none";
}
const btn = document.getElementById(`settings-modal-${panel.id}-panel-btn`);
if(btn) {
btn.style.backgroundColor = "";
}
});
}
FlatMMOPlus.prototype.onMessageSent = function(message) {
if(this.debug) {
console.log(`FM+ onMessageSent: ${message}`);
}
let interrupt = false;
this.forEachPlugin((plugin) => {
if(typeof plugin.onMessageSent === "function") {
if(plugin.onMessageSent(message)) {
interrupt = true;
};
}
});
return interrupt;
}
FlatMMOPlus.prototype.onMessageReceived = function(data) {
if(this.debug) {
console.log(`FM+ onMessageReceived: ${data}`);
}
if(data) {
this.forEachPlugin((plugin) => {
if(typeof plugin.onMessageReceived === "function") {
plugin.onMessageReceived(data);
}
});
if(data.startsWith("LOGGED_IN")) {
this.onLogin();
} else if (data.startsWith("YELL")) {
const split = data.substring("YELL=".length).split("~");
const chatData = {
username: split[0].slice(0, -7).trim(),
tag: split[1],
sigil: split[2],
color: split[3],
message: split[4],
yell: true
}
this.onChat(chatData);
} else if (data.startsWith("CHAT_LOCAL_MESSAGE=")) {
const split = data.substring("CHAT_LOCAL_MESSAGE=".length).split("~");
let [sender, message] = split[1].split(" yelled: ");
//Server messages don't have the "yelled"
if (!message) {
message = sender;
sender = "";
}
const chatData = {
username: sender,
tag: "none",
sigil: "none",
color: split[0],
message: message,
yell: true
}
this.onChat(chatData);
} else if (data.startsWith("CHAT=")) {
const split = data.substring("CHAT=".length).split("~");
const chatData = {
username: split[0],
tag: split[1],
sigil: split[2],
color: split[3],
message: split[4],
yell: false
}
this.onChat(chatData);
} else if (data.startsWith("REFRESH_PLAYER_HP_BAR=")) {
const split = data.substring("REFRESH_PLAYER_HP_BAR=".length).split("~");
if(split[0] === Globals.local_username) {
if(this.isFighting && split[3] === "false") {
this.onFightEnded();
} else if (!this.isFighting && split[3] === "true") {
this.onFightStarted();
}
}
} else if (data.startsWith(`SET_PLAYER_ANIMATION=${Globals.local_username}`)) {
const split = data.substring("SET_PLAYER_ANIMATION=".length).split("~");
this.currentAction = split[1];
this.onActionChanged();
} else if (data.startsWith("CUSTOM")) {
const [sender, content] = data.substring("CUSTOM=".length).split("~");
let plugin = "unknown";
let command = "unknown";
let payload = content;
const splitContent = content.split(":");
if (splitContent.length >= 3) {
plugin = splitContent[0];
command = splitContent[1];
payload = splitContent.slice(2).join(":");
}
this.onCustomReceived(sender, plugin, command, payload);
}
}
}
FlatMMOPlus.prototype.onCustomReceived = function(sender, script, command, payload) {
if(this.debug) {
console.log(`FMMO+ onCustomReceived: ${sender} ${message}`);
}
this.forEachPlugin((plugin) => {
if (typeof plugin.onCustomReceived === "function") {
plugin.onCustomReceived(sender, script, command, payload);
}
})
}
FlatMMOPlusPlugin.prototype.sendCustom = function(user, command, payload) {
let message = `CUSTOM=${user}~${this.id}`;
message += command ? `:${command}` : ":ping";
message += payload ? `:${payload}` : ":";
window.FlatMMOPlus.sendMessage(message);
}
FlatMMOPlus.prototype.sendCustomMessage = function(toPlayer, content) {
if(this.debug) {
console.log(`FMMO+ sendCustomMessage`, toPlayer, opts);
}
const message = `CUSTOM=${toPlayer}~${content}`;
this.sendMessage(message);
}
FlatMMOPlus.prototype.onLogin = function() {
if(this.debug) {
console.log(`FM+ onLogin`);
}
logFancy("login detected");
document.getElementById("chat").insertAdjacentHTML("beforeend",`<div style="color: white;">
<span><strong style="color:cyan">FYI: </strong> Use the /help command to see information on available chat commands.</span>
<br>
</div>`)
this.forEachPlugin((plugin) => {
if(typeof plugin.onLogin === "function") {
plugin.onLogin();
}
});
this.loggedIn = true;
setTimeout(() => {
Object.defineProperty(players[Globals.local_username], 'in_combat_ticker', {
get: function() {
return FlatMMOPlus.in_combat_ticker;
},
set: function(newValue) {
FlatMMOPlus.in_combat_ticker = newValue;
if(newValue === 0) {
window.FlatMMOPlus.onFightEnded();
}
}
});
}, 3000);
//Chat auto scroll is always true for now
chat_div_element.scrollTop = chat_div_element.scrollHeight;
const original_paint = window.paint_effects;
window.paint_effects = function() {
original_paint();
try {
window.FlatMMOPlus.onPaint();
} catch (error) {
console.warn("Error on FlatMMO+ onPaint", error.message)
}
};
const originalLayer1 = window.paint_layer_1;
window.paint_layer_1 = function() {
originalLayer1();
try {
window.FlatMMOPlus.onPaintObjects();
} catch (error) {
console.warn("Error on FlatMMO+ paint_layer_1", error.message)
}
};
const originalGroundItems = window.paint_ground_items;
window.paint_ground_items = function() {
originalGroundItems();
try {
window.FlatMMOPlus.onPaintNpcs();
} catch (error) {
console.warn("Error on FlatMMO+ paint_ground_items", error.message)
}
};
}
FlatMMOPlus.prototype.onChat = function(data) {
if(this.debug) {
console.log(`FM+ onChat`, data);
}
this.forEachPlugin((plugin) => {
if(typeof plugin.onChat === "function") {
plugin.onChat(data);
}
});
}
FlatMMOPlus.prototype.addNotification = function(name, imageSrc, title = "", text = "", ticks = 900, color = "white", bgColor = "black", titleColor, progressColor = "red") {
const img = new Image();
img.src = imageSrc;
img.onload = () => {
this.notifications[name] = {
image: img,
title,
text,
color,
titleColor: titleColor || color,
bgColor,
progressColor,
ticksFull: ticks,
ticks
}
}
}
FlatMMOPlus.prototype.paintNotifications = function() {
const nots = Object.keys(this.notifications);
if(nots.length === 0) return;
if(this.handler.config.hoverNotifications) {
this.paintHoverNotifications(nots);
} else {
this.paintBlockNotifications();
}
}
FlatMMOPlus.prototype.paintHoverNotifications = function(notifications) {
const CIRCLE_R = 22;
const ARC_R = 30;
const ARC_W = 5;
const SPACING = 12;
const WIDGET_W = ARC_R * 2 + SPACING;
const FADE_TICKS = 60;
// Center the row of widgets horizontally and vertically
const totalW = notifications.length * WIDGET_W - SPACING;
let cx = canvasWidth / 2 - totalW / 2 + ARC_R;
const CY = this.handler.config.hoverNotificationHeight * TILE_SIZE - (ARC_R + ARC_W + 20);
const mx = mouse_over_now.x;
const my = mouse_over_now.y;
let tooltip = null; // {cx, text} — drawn last so it's always on top
for (let notification in this.notifications) {
const not = this.notifications[notification];
not.ticks--;
if(not.ticks <= 0) {
delete this.notifications[notification];
return;
}
const alpha = this.handler.config.hoverNotificationAlpha === 100 ? not.ticks < FADE_TICKS ? Math.max(0, not.ticks / FADE_TICKS) : 1.0 : this.handler.config.hoverNotificationAlpha;
ctx.save();
ctx.globalAlpha = alpha;
// Dark backing disc
ctx.beginPath();
ctx.arc(cx, CY, ARC_R + ARC_W, 0, Math.PI * 2);
ctx.fillStyle = "rgba(0,0,0,0.6)";
ctx.fill();
// Clip icon to inner circle
ctx.save();
ctx.beginPath();
ctx.arc(cx, CY, CIRCLE_R, 0, Math.PI * 2);
ctx.clip();
const imgWidth = CIRCLE_R * 2;
ctx.drawImage(not.image, cx - CIRCLE_R, CY - CIRCLE_R, imgWidth, imgWidth * (not.image.naturalHeight / not.image.naturalWidth));
ctx.restore();
// Empty arc track
ctx.beginPath();
ctx.arc(cx, CY, ARC_R, -Math.PI / 2, Math.PI * 1.5);
ctx.strokeStyle = "rgba(255,255,255,0.15)";
ctx.lineWidth = ARC_W;
ctx.lineCap = "butt";
ctx.stroke();
// Filled progress arc (clockwise from top)
const endAngle = -Math.PI / 2 + (not.ticks / not.ticksFull) * Math.PI * 2;
ctx.beginPath();
ctx.arc(cx, CY, ARC_R, -Math.PI / 2, endAngle);
ctx.strokeStyle = not.progressColor;
ctx.lineWidth = ARC_W;
ctx.lineCap = "round";
ctx.stroke();
ctx.restore();
// Queue tooltip if mouse is hovering this orb
const dx = mx - cx, dy = my - CY;
if (dx * dx + dy * dy <= (ARC_R + ARC_W) * (ARC_R + ARC_W)) {
tooltip = { cx, not };
}
cx += WIDGET_W;
}
// Draw tooltip on top of all orbs
if (tooltip) {
const tx = tooltip.cx;
const ty = CY - (ARC_R + ARC_W + 14);
ctx.save();
const PAD = 16;
ctx.textAlign = "center";
const tw = ctx.measureText(tooltip.not.title).width;
const txw = ctx.measureText(tooltip.not.text).width;
const rectW = tw > txw ? tw : txw;
const th = 16;
const txh = 13;
ctx.font = "16px sans-serif";
ctx.fillStyle = tooltip.not.bgColor;
ctx.fillRect(tx - rectW / 2 - PAD, ty - th - PAD / 2, rectW + PAD * 2, th + txh + PAD);
ctx.fillStyle = tooltip.not.titleColor;
ctx.fillText(tooltip.not.title, tx, ty);
ctx.fillStyle = tooltip.not.color;
ctx.font = "13px sans-serif";
ctx.fillText(tooltip.not.text, tx, ty + th);
ctx.restore();
}
}
FlatMMOPlus.prototype.paintBlockNotifications = function() {
const PADDING = 10;
let yOffset = 0;
let y = 13 * TILE_SIZE;
for (let notification in this.notifications) {
const not = this.notifications[notification];
if(not.ticks <= 0) {
delete this.notifications[notification];
return;
}
not.ticks--;
ctx.font = "20px serif";
ctx.fillStyle = "white";
ctx.globalAlpha = 0.2;
ctx.fillRect(21 * TILE_SIZE, y - yOffset, TILE_SIZE * 3 - PADDING, TILE_SIZE - PADDING);
ctx.globalAlpha = 1.0;
ctx.drawImage(not.image, 21 * TILE_SIZE + PADDING, y + 10 - yOffset, 32, 32);
ctx.fillStyle = not.color;
ctx.fillText(not.title, 21 * TILE_SIZE + 50, y + 20 - yOffset);
ctx.font = "16px serif";
ctx.fillText(not.text, 21 * TILE_SIZE + 50, y + 40 - yOffset);
yOffset += TILE_SIZE;
}
}
//After vanilla paint
FlatMMOPlus.prototype.onPaint = function() {
if(this.debug) {
console.log("FM+ onPaint");
}
this.forEachPlugin((plugin) => {
if(typeof plugin.onPaint === "function") {
plugin.onPaint();
}
});
this.paintNotifications();
}
//Between paint_layer_1 and paint_map_objects_lower_shadows
FlatMMOPlus.prototype.onPaintObjects = function() {
if(this.debug) {
console.log("FM+ onPaintObjects");
}
this.forEachPlugin((plugin) => {
if(typeof plugin.onPaintObjects === "function") {
plugin.onPaintObjects();
}
});
}
//Between paint_ground_items and paint_npcs
FlatMMOPlus.prototype.onPaintNpcs = function() {
if(this.debug) {
console.log("FM+ onPaintNpcs");
}
this.forEachPlugin((plugin) => {
if(typeof plugin.onPaintNpcs === "function") {
plugin.onPaintNpcs();
}
});
}
FlatMMOPlus.prototype.onPanelChanged = function(panelBefore, panelAfter) {
if(this.debug) {
console.log(`FM+ onPanelChanged "${panelBefore}" -> "${panelAfter}"`);
}
this.forEachPlugin((plugin) => {
if(typeof plugin.onPanelChanged === "function") {
plugin.onPanelChanged(panelBefore, panelAfter);
}
});
}
FlatMMOPlus.prototype.onSettingsPanelChanged = function(panelBefore, panelAfter) {
if(this.debug) {
console.log(`FM+ onSettingsPanelChanged "${panelBefore}" -> "${panelAfter}"`);
}
if(panelAfter === "plugins") {
this.refreshPanel("plugins");
}
this.forEachPlugin((plugin) => {
if(typeof plugin.onSettingsPanelChanged === "function") {
plugin.onSettingsPanelChanged(panelBefore, panelAfter);
}
});
}
FlatMMOPlus.prototype.onMapChanged = function(mapBefore, mapAfter) {
if(this.debug) {
console.log(`FMMO+ onMapChanged "${mapBefore}" -> "${mapAfter}"`);
}
this.forEachPlugin((plugin) => {
if(typeof plugin.onMapChanged === "function") {
plugin.onMapChanged(mapBefore, mapAfter);
}
});
}
FlatMMOPlus.prototype.onInventoryChanged = function(inventoryBefore, inventoryAfter) {
if(this.debug) {
console.log(`FMMO+ onInventoryChanged "${inventoryBefore}" -> "${inventoryAfter}"`);
}
this.forEachPlugin((plugin) => {
if(typeof plugin.onInventoryChanged === "function") {
plugin.onInventoryChanged(inventoryBefore, inventoryAfter);
}
});
}
FlatMMOPlus.prototype.onDamageTaken = function(hpBefore, hpAfter) {
if(this.debug) {
console.log(`FMMO+ onDamageTaken "${hpBefore}" -> "${hpAfter}"`);
}
this.forEachPlugin((plugin) => {
if(typeof plugin.onDamageTaken === "function") {
plugin.onDamageTaken(hpBefore, hpAfter);
}
});
}
FlatMMOPlus.prototype.onFightStarted = function() {
if(this.debug) {
console.log(`FMMO+ onFightStarted`);
}
this.isFighting = true;
this.forEachPlugin((plugin) => {
if(typeof plugin.onFightStarted === "function") {
plugin.onFightStarted();
}
});
}
FlatMMOPlus.prototype.onFightEnded = function() {
if(this.debug) {
console.log(`FMMO+ onFightEnded`);
}
this.isFighting = false;
this.forEachPlugin((plugin) => {
if(typeof plugin.onFightEnded === "function") {
plugin.onFightEnded();
}
});
}
FlatMMOPlus.prototype.onActionChanged = function() {
if(this.debug) {
console.log(`FMMO+ onActionChanged`);
}
this.forEachPlugin((plugin) => {
if(typeof plugin.onActionChanged === "function") {
plugin.onActionChanged();
}
});
}
FlatMMOPlus.prototype.init = function(){
//Tries to hook into the websocket messages
const hookIntoOnMessage = () => {
try {
if (typeof this.original_onmessage === "function") {
Globals.websocket.onmessage = (event) => {
if(event.data.startsWith("SET_MAP")) {
const mapBefore = current_map;
this.original_onmessage(event);
const mapAfter = current_map;
this.onMapChanged(mapBefore, mapAfter);
return;
} else if (event.data.startsWith("SET_INVENTORY_ITEMS")) {
const inventoryBefore = items;
this.original_onmessage(event);
const inventoryAfter = items;
this.onInventoryChanged(inventoryBefore, inventoryAfter);
return;
} else if (event.data.startsWith(`REFRESH_PLAYER_HP_BAR=${Globals.local_username}`)) {
const hpBefore = players[Globals.local_username].hp;
this.original_onmessage(event);
const hpAfter = players[Globals.local_username].hp;
if(hpAfter < hpBefore) {
this.onDamageTaken(hpBefore, hpAfter);
}
}
this.original_onmessage(event);
this.onMessageReceived(event.data);
}
}
if(typeof this.original_sendmessage === "function") {
Globals.websocket.send = (message) => {
let canSend = !this.onMessageSent(message);
if(canSend) {
this.original_sendmessage.call(Globals.websocket, message);
}
}
}
return true;
} catch (err) {
console.error("Had trouble hooking into websocket...");
return false;
}
}
//This will call itthis
(function(){
if(!hookIntoOnMessage()) {
// try once more
setTimeout(hookIntoOnMessage, 40);
}
})()
window.addEventListener("keydown", this.fmpKeyDown, false);
document.getElementById("canvas").insertAdjacentHTML("beforeend",'<div id="FMPNotifications"></div>')
// hook into switch_panels, which is called when the main panel is changed. This is used for custom panels.
window.switch_panels = (id) => {
let panelBefore = this.currentPanel;
this.original_switch_panels(id);
this.currentPanel = id;
let panelAfter = id;
this.onPanelChanged(panelBefore, panelAfter);
}
window.settings_modal_tab = (id) => {
let panelBefore = this.currentSettingsPanel;
this.hideCustomPanels();
//I'm using links because some panels don't have a button
if(document.getElementById(`settings-modal-${id}-panel-btn`)) {
this.original_settings_modal_tab(id);
} else {
this.original_settings_modal_tab("links");
document.getElementById('settings-modal-links-panel-btn').style.backgroundColor = '';
document.getElementById('settings-modal-links-panel').style.display = 'none'
document.getElementById(`settings-modal-${id}-panel`).style.display = '';
}
this.currentSettingsPanel = id;
let panelAfter = id;
this.onSettingsPanelChanged(panelBefore, panelAfter);
}
window.enter_pressed = function() {
const message = chat_ele.value.trim();
if (message === "") return;
chat_ele.value = "";
request_unfocus_chatbox();
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);
}
//FMP only excutes if this is a valid command, so there is no reason to check if it exists
if (window.FlatMMOPlus.handleCustomChatCommand(command, data)) {
return;
}
}
//If it is not a valid command or isn't a command at all it will send the message
Globals.websocket.send('CHAT=' + message);
}
//Remove vanilla keyboard menu
document.getElementById("settings-modal-keyboard-panel-btn").style.display = "none";
this.addPanel("hotkeys", "Hotkeys", "", true);
this.addPanel("plugins", "Plugins", () => {
let content = "";
this.forEachPlugin(plugin => {
let id = plugin.id;
let name = "An FlatMMO+ 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="flatmmoplus-plugin-box-${id}" class="flatmmoplus-plugin-box">
<div style="display:flex;justify-content: space-between; align-items:center;">
<div>
<strong style="font-size: large;"><u>${name||id}</u></strong>(by ${author})<br />
<span>${description}</span><br />
</div>
${plugin.opts.config ? `<div class="flatmmoplus-plugin-settings-button">
<button onclick="document.querySelector('#flatmmoplus-plugin-box-${id} .flatmmoplus-plugin-config-section').classList.toggle('displaynone')">Settings</button>
</div>` : ""}
</div>
<div class="flatmmoplus-plugin-config-section displaynone">
<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_PANEL.includes(cfg.type)) {
content += `<div onclick="FlatMMOPlus.setPanel('${cfg.panel}')" style="cursor: pointer;">
<h2 style="text-align: center;">${cfg.label}</h2>
</div>`
} else if(CONFIG_TYPES_BOOLEAN.includes(cfg.type)) {
content += `
<div>
<label for="flatmmoplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
<input id="flatmmoplus-config-${plugin.id}-${cfg.id}" type="checkbox" onchange="FlatMMOPlus.setPluginConfigUIDirty('${id}', true, '${cfg.id}')" />
</div>
`;
}
else if(CONFIG_TYPES_INTEGER.includes(cfg.type)) {
content += `
<div>
<label for="flatmmoplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
<input id="flatmmoplus-config-${plugin.id}-${cfg.id}" type="number" step="1" min="${cfg.min || ''}" max="${cfg.max || ''}" onchange="FlatMMOPlus.setPluginConfigUIDirty('${id}', true, '${cfg.id}')" />
</div>
`;
}
else if(CONFIG_TYPES_FLOAT.includes(cfg.type)) {
content += `
<div>
<label for="flatmmoplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
<input id="flatmmoplus-config-${plugin.id}-${cfg.id}" type="number" step="${cfg.step || ''}" min="${cfg.min || ''}" max="${cfg.max || ''}" onchange="FlatMMOPlus.setPluginConfigUIDirty('${id}', true, '${cfg.id}');" />
</div>
`;
} else if (CONFIG_TYPES_RANGE.includes(cfg.type)) {
content += `<div>
<label for="flatmmoplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
<div>
<input id="flatmmoplus-config-${plugin.id}-${cfg.id}" type="range" step="${cfg.step || ''}" min="${cfg.min || ''}" max="${cfg.max || ''}" onchange="FlatMMOPlus.setPluginConfigUIDirty('${id}', true, '${cfg.id}');" oninput="document.getElementById('flatmmoplus-config-${plugin.id}-${cfg.id}-value').innerText = this.value" />
<span id="flatmmoplus-config-${plugin.id}-${cfg.id}-value"></span>
</div>
</div>`;
}
else if(CONFIG_TYPES_STRING.includes(cfg.type)) {
content += `
<div>
<label for="flatmmoplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
<textarea id="flatmmoplus-config-${plugin.id}-${cfg.id}" type="text" maxlength="${cfg.max || ''}" onchange="FlatMMOPlus.setPluginConfigUIDirty('${id}', true, '${cfg.id}')" rows="5" autocomplete="off" spellcheck="false"></textarea>
</div>
`;
}
else if(CONFIG_TYPES_COLOR.includes(cfg.type)) {
content += `
<div>
<label for="flatmmoplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
<input id="flatmmoplus-config-${plugin.id}-${cfg.id}" type="color" onchange="FlatMMOPlus.setPluginConfigUIDirty('${id}', true, '${cfg.id}')" />
</div>
`;
}
else if(CONFIG_TYPES_LIST.includes(cfg.type)) {
content += `
<div>
<label>${cfg.label || cfg.id}</label>
<div class="fmp-list-div" id="flatmmoplus-config-${plugin.id}-${cfg.id}"></div>
<button style="cursor:pointer;font-size: 1rem;" onclick="FlatMMOPlus.newListField('${plugin.id}', '${cfg.id}')">Add new field</button>
</div>
`;
}
else if(CONFIG_TYPES_RELATION.includes(cfg.type)) {
content += `
<div>
<label>${cfg.label || cfg.id}</label>
<div class="fmp-objectLabel">
<label>${cfg.key || "Key"}</label>
<label>${cfg.value || "Value"}</label>
</div>
<div class="fmp-list-div" id="flatmmoplus-config-${plugin.id}-${cfg.id}"></div>
<button style="cursor:pointer;font-size: 1rem;" onclick="FlatMMOPlus.newObjectField('${plugin.id}', '${cfg.id}')">Add new field</button>
</div>
`;
}
else if(CONFIG_TYPES_PRIORITY.includes(cfg.type)) {
content += `
<div>
<label>${cfg.label || cfg.id}</label>
<div class="fmp-priority-div" id="flatmmoplus-config-${plugin.id}-${cfg.id}" data-pluginid="${plugin.id}" data-configid="${cfg.id}"></div>
${cfg.canAddNew ? `<button style="cursor:pointer;font-size: 1rem;" onclick="FlatMMOPlus.newPriorityField('${plugin.id}', '${cfg.id}')">Add new field</button>` : ""}
</div>
`;
}
else if(CONFIG_TYPES_SELECT.includes(cfg.type)) {
content += `
<div>
<label for="flatmmoplus-config-${plugin.id}-${cfg.id}">${cfg.label || cfg.id}</label>
<div>
<select id="flatmmoplus-config-${plugin.id}-${cfg.id}" onchange="FlatMMOPlus.setPluginConfigUIDirty('${id}', true,'${cfg.id}')">
`;
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>
</div>`;
}
else if (CONFIG_TYPES_BUTTON.includes(cfg.type)) {
content += `
<div>
<label>${cfg.label || cfg.id}</label>
<button onclick="${cfg.func}()" style="cursor: pointer;" class="${cfg.class || "fmpConfigBtn"}" style="text-align: center;">${cfg.text}</button>
</div>
`;
}
});
content += `
<div style="grid-column: span 2">
<button id="flatmmoplus-configbutton-${plugin.id}-reload" onclick="FlatMMOPlus.loadPluginConfigs('${id}')">Reload</button>
<button id="flatmmoplus-configbutton-${plugin.id}-apply" onclick="FlatMMOPlus.savePluginConfigs('${id}')">Apply</button>
</div>
`;
}
content += "</div>";
content += "</div>";
});
return content;
}, true);
this.handler = new handlerPlugin()
this.registerPlugin(this.handler);
/** Priority Config Type */
document.addEventListener('dragstart', (e) => {
if (e.target.classList.contains('fmp-priority-item')) {
e.target.classList.add('fmp-priority-dragging');
}
});
document.addEventListener('dragend', (e) => {
if (e.target.classList.contains('fmp-priority-item')) {
e.target.classList.remove('fmp-priority-dragging');
}
});
document.addEventListener("dragover", (e) => {
const currentItem = e.target.closest(".fmp-priority-item");
if (!currentItem) return;
e.preventDefault()
const draggingItem = document.querySelector(".fmp-priority-dragging")
const list = currentItem.closest(".fmp-priority-div");
if(!draggingItem || !list || list !== draggingItem.parentElement) {
return;
};
const siblings = [...list.querySelectorAll('.fmp-priority-item:not(.fmp-priority-dragging)')];
const nextSibling = siblings.find(sibling => {
const box = sibling.getBoundingClientRect();
return e.clientY <= box.top + box.height / 2;
});
if (nextSibling) {
list.insertBefore(draggingItem, nextSibling);
} else {
list.appendChild(draggingItem);
}
const pluginId = list.getAttribute("data-pluginid");
const configId = list.getAttribute("data-configid");
window.FlatMMOPlus.plugins[pluginId].changedConfigs.add(configId);
window.FlatMMOPlus.setPluginConfigUIDirty(pluginId, true, configId);
});
/*Hotkeys Setup*/
hotkeyOverride = JSON.parse(localStorage.getItem("FMP-hotkeys") || '{}');
defaultHotkeys.forEach(hotkey => window.FlatMMOPlus.registerHotkey(hotkey));
document.getElementById("fmp-hotkeysContainer-Preset 1").insertAdjacentHTML("beforeend", `<button onclick="Globals.websocket.send('SAVE_PRESET=F6')"><img src="images/icons/save.png"> Save Preset</button>`);
document.getElementById("fmp-hotkeysContainer-Preset 2").insertAdjacentHTML("beforeend", `<button onclick="Globals.websocket.send('SAVE_PRESET=F7')"><img src="images/icons/save.png"> Save Preset</button>`);
document.getElementById("fmp-hotkeysContainer-Preset 3").insertAdjacentHTML("beforeend", `<button onclick="Globals.websocket.send('SAVE_PRESET=F8')"><img src="images/icons/save.png"> Save Preset</button>`)
logFancy(`(v${this.version}) initialized.`);
if(this.loggedIn === false && Object.keys(item_sell_prices).length !== 0) {
this.onLogin();
}
}
// Add to window and init
window.AnimationSheetPlus = AnimationSheetPlus;
window.FlatMMOPlusPlugin = FlatMMOPlusPlugin;
window.FlatMMOPlus = new FlatMMOPlus();
if (typeof unsafeWindow !== 'undefined' && window !== unsafeWindow) {
unsafeWindow.AnimationSheetPlus = window.AnimationSheetPlus;
unsafeWindow.FlatMMOPlusPlugin = window.FlatMMOPlusPlugin;
unsafeWindow.FlatMMOPlus = window.FlatMMOPlus;
}
window.FlatMMOPlus.customChatCommands["help"] = (command, data='') => {
let help;
if(data && data!="help") {
let helpContent = window.FlatMMOPlus.customChatHelp[data.trim()] || "No help content was found for this command.";
help = `<div style="color: white;">
<strong><u>Command Help:</u></strong><br />
<span><strong style="color:cyan">/${data}:</strong> <span>${helpContent}</span>
<br>`
}
else {
help = `<div style="color: white;">
<strong><u>Command Help:</u></strong><br />
<strong>Available Commands:</strong> <span>${Object.keys(window.FlatMMOPlus.customChatCommands).sort().map(s => "/"+s).join(" ")}</span><br />
<span>Use the /help command for more information about a specific command: /help <command></span>
</div>`
}
window.FlatMMOPlus.showWarning(help, "white");
//Chat auto scroll is always true for now
chat_div_element.scrollTop = chat_div_element.scrollHeight;
};
//flatChat overrides this
window.FlatMMOPlus.registerCustomChatCommand(["clear", "clean"], (command, data='') => {
document.getElementById("chat").innerHTML = "";
}, "Clears all messages in chat.");
window.FlatMMOPlus.registerCustomChatCommand(["y", "yell"], (command, data='') => {
Globals.websocket.send('CHAT=/yell ' + data);
}, "Chat to everyone on server.<br><strong>Usage:</strong> /yell [message]");
window.FlatMMOPlus.registerCustomChatCommand("pm", (command, data='') => {
Globals.websocket.send('CHAT=/pm ' + data);
}, "Send a private message to someone.<br><strong>Usage:</strong> /pm [username] [message]");
window.FlatMMOPlus.registerCustomChatCommand("stuck", (command, data='') => {
Globals.websocket.send('CHAT=/stuck');
}, "`Use if your character is stuck and cannot move.");
window.FlatMMOPlus.registerCustomChatCommand(["players","who"], (command, data='') => {
Globals.websocket.send('CHAT=/players');
}, "Show all players online.");
window.FlatMMOPlus.registerCustomChatCommand("ohelp", (command, data='') => {
Globals.websocket.send(`CHAT=/help`);
}, `Shows the original vanilla /help`);
window.FlatMMOPlus.registerCustomChatCommand("collections", () => {
Globals.websocket.send(`CHAT=/collections`);
}, `Access to your collection log`);
window.FlatMMOPlus.registerCustomChatCommand("detach", () => {
document.getElementById("chat").style.display = "none";
document.querySelector('[data-settings="hide_chat"]').checked = true;
openChatPopup();
}, `Detach the chat from the window`);
window.FlatMMOPlus.registerCustomChatCommand("timers", () => {
openTimersPopup();
}, `Opens timers menu`);
window.FlatMMOPlus.registerCustomChatCommand("autoyell", (command, data="on") => {
Globals.websocket.send("CHAT=/autoyell " + data);
}, `Turns auto yell on`);
window.FlatMMOPlus.registerCustomChatCommand("tick", () => {
this.showWarning(`The current action takes ${progress_bar_target + 1} ticks (${(progress_bar_target + 1) / 2} seconds)`);
}, `Shows the time needed to complete the current action`);
window.FlatMMOPlus.registerCustomChatCommand("trade", (command, data='') => {
if (data === "") {
FlatMMOPlus.showWarning("You need to specify the username", "red");
return;
}
Globals.websocket.send("SEND_TRADE_REQUEST=" + data.replaceAll("_", " "));
}, `Send a trade request if the player is in the same map.<br><b>Usage:</b>/trade [username]`);
window.FlatMMOPlus.init();
})();