TORN: TornTools - Opened Supply Pack Value

Display the value of the supply pack you just opened.

Terá de instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Terá de instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Terá de instalar uma extensão como Tampermonkey para instalar este script.

Terá de instalar uma extensão de gestão de scripts de utilizador para instalar este script.

(Já tenho um gestor de scripts de utilizador, deixe-me instalá-lo!)

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

(Já tenho um gestor de estilos de utilizador, deixe-me instalá-lo!)

// ==UserScript==
// @name             TORN: TornTools - Opened Supply Pack Value
// @namespace        torntools.opened-supply-pack-value
// @version          1.0.5
// @author           DeKleineKobini [2114440] and the TornTools team
// @description      Display the value of the supply pack you just opened.
// @license          GPL-3.0-or-later
// @icon             https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @supportURL       https://github.com/Mephiles/torntools_extension/issues
// @match            https://*.torn.com/item.php*
// @connect          torntools.tornplayground.eu
// @grant            GM.getValue
// @grant            GM.info
// @grant            GM.setValue
// @grant            GM.xmlHttpRequest
// @grant            GM_addStyle
// @grant            unsafeWindow
// @run-at           document-end
// @contributionURL  https://buymeacoffee.com/dekleinekobini
// ==/UserScript==

(function() {
	"use strict";
	var s = new Set();
	var _css = async (t) => {
		if (s.has(t)) return;
		s.add(t);
		((c) => {
			if (typeof GM_addStyle === "function") GM_addStyle(c);
			else (document.head || document.documentElement).appendChild(document.createElement("style")).append(c);
		})(t);
	};
	var FEATURE_MANAGER;
	var ttStorage;
	var SCRIPT_INJECTOR;
	var RUNTIME_INFORMATION;
	var RUNTIME_STORAGE;
	var OFFLOAD_SERVICE;
	var DATA_FETCHER;
	var ITEM_RESOLVER;
	var EVENT_HANDLER;
	function setFeatureManager(featureManager) {
		FEATURE_MANAGER = featureManager;
	}
	function setTTStorage(storage) {
		ttStorage = storage;
	}
	function setScriptInjector(scriptInjector) {
		SCRIPT_INJECTOR = scriptInjector;
	}
	function setRuntimeInformation(runtimeInformation) {
		RUNTIME_INFORMATION = runtimeInformation;
	}
	function setRuntimeStorage(runtimeStorage) {
		RUNTIME_STORAGE = runtimeStorage;
	}
	function setOffloadService(offloadService) {
		OFFLOAD_SERVICE = offloadService;
	}
	function setDataFetcher(dataFetcher) {
		DATA_FETCHER = dataFetcher;
	}
	function setStaticItemResolver(staticItemResolver) {
		ITEM_RESOLVER = staticItemResolver;
	}
	function setEventHandler(eventHandler) {
		EVENT_HANDLER = eventHandler;
	}
	_css(".tt-opened-supply-pack-value-text{color:#82c91e;padding-top:.4em;display:block}");
	_css(".tt-loading-placeholder{content:var(--default-preloader-url,url(https://www.torn.com/images/v2/main/ajax-loader.gif));margin:0 auto;padding:10px;display:none}.tt-loading-placeholder.active{display:block}");
	var SCRIPT_TYPE = (() => {
		if (typeof window === "undefined" || window.location.href.endsWith("/_generated_background_page.html")) return "BACKGROUND";
		else if (typeof browser === "object" && browser.action) return "POPUP";
		else if (typeof location !== "undefined" && location.protocol?.includes("extension")) return "INTERNAL_CONTENT";
		else return "CONTENT";
	})();
	function sleep(millis) {
		return new Promise((resolve) => setTimeout(resolve, millis));
	}
	var TO_MILLIS = {
		SECONDS: 1e3,
		MINUTES: 6e4,
		HOURS: 36e5,
		DAYS: 864e5
	};
	function isIntNumber(number) {
		if (number === null) return false;
		if (number.match(/[a-zA-Z]/)) return false;
		const _number = parseFloat(number.toString());
		return !Number.isNaN(_number) && Number.isFinite(_number) && _number % 1 === 0;
	}
	function getUUID() {
		return `_${Math.random().toString(36).slice(2, 11)}`;
	}
	function getCookie(cname) {
		const name = `${cname}=`;
		for (let cookie of decodeURIComponent(document.cookie).split(";")) {
			cookie = cookie.trimStart();
			if (cookie.includes(name)) return cookie.substring(name.length);
		}
		return "";
	}
	function toNumericVersion(version) {
		return parseInt(version.split(".").map((part) => part.padStart(3, "0")).join("").padEnd(9, "9"));
	}
	function isTabFocused() {
		return document.hasFocus();
	}
	function checkListener(listener, entry) {
		const element = listener.parent.querySelector(listener.selector);
		if (!(listener.invert ? !element : !!element)) return false;
		if (listener.timeoutId) clearTimeout(listener.timeoutId);
		entry.listeners.delete(listener);
		listener.resolve(listener.invert ? true : element);
		cleanupEntryIfEmpty(entry);
		return true;
	}
	function cleanupEntryIfEmpty(entry) {
		if (entry.listeners.size > 0) return;
		entry.observer.disconnect();
		observerRegistry.delete(entry.parent);
	}
	function removeListenerFromRegistry(listener) {
		const entry = observerRegistry.get(listener.parent);
		if (!entry) return;
		entry.listeners.delete(listener);
		cleanupEntryIfEmpty(entry);
	}
	function requireElement(selector, attributes = {}) {
		const options = {
			invert: false,
			parent: document,
			timeout: TO_MILLIS.SECONDS * 5,
			observerOptions: {
				childList: true,
				subtree: true
			},
			...attributes
		};
		const error = new Error("Maximum cycles reached.");
		return new Promise((resolve, reject) => {
			const element = options.parent.querySelector(selector);
			if (options.invert && !element) {
				resolve(true);
				return;
			} else if (!options.invert && element) {
				resolve(element);
				return;
			}
			const timeoutId = options.timeout > 0 ? window.setTimeout(() => {
				removeListenerFromRegistry(listener);
				reject(error);
			}, options.timeout) : null;
			const listener = {
				selector,
				invert: options.invert,
				parent: options.parent,
				resolve,
				reject,
				timeoutId
			};
			getOrCreateObserverEntry(options.parent).listeners.add(listener);
		});
	}
	var observerRegistry = new Map();
	function getOrCreateObserverEntry(parent) {
		const existing = observerRegistry.get(parent);
		if (existing) return existing;
		const observer = new MutationObserver(() => {
			const entry = observerRegistry.get(parent);
			if (!entry) return;
			entry.listeners.forEach((listener) => checkListener(listener, entry));
		});
		const entry = {
			parent,
			observer,
			listeners: new Set()
		};
		observerRegistry.set(parent, entry);
		observer.observe(parent, {
			childList: true,
			subtree: true
		});
		return entry;
	}
	function requireDOMInteractive() {
		return new Promise((resolve) => {
			if (document.readyState === "loading") document.addEventListener("readystatechange", () => resolve(), { once: true });
			else resolve();
		});
	}
	var mobile;
	var tablet;
	var hasSidebar;
	var tabletHorizontal;
	var tabletVertical;
	function elementBuilder(options) {
		if (typeof options === "string") return document.createElement(options);
		else if (typeof options === "object") {
			options = {
				id: void 0,
				class: void 0,
				text: void 0,
				html: void 0,
				value: void 0,
				href: void 0,
				children: [],
				attributes: {},
				events: {},
				style: {},
				dataset: {},
				...options
			};
			const newElement = document.createElement(options.type);
			if (options.id) newElement.id = options.id;
			if (options.class) newElement.className = Array.isArray(options.class) ? options.class.filter((name) => !!name).join(" ") : options.class.trim();
			if (options.text !== void 0) newElement.textContent = options.text.toString();
			if (options.html) newElement.innerHTML = options.html;
			if (options.value && "value" in newElement) {
				if (typeof options.value === "function") newElement.value = options.value();
				else newElement.value = options.value;
			}
			if (options.href && "href" in newElement) newElement.href = options.href;
			for (const child of options.children?.filter((child) => !!child) || []) if (typeof child === "string") newElement.appendChild(document.createTextNode(child));
			else newElement.appendChild(child);
			if (options.attributes) {
				let attributes = options.attributes;
				if (typeof attributes === "function") attributes = attributes();
				for (const attribute in attributes) newElement.setAttribute(attribute, attributes[attribute].toString());
			}
			for (const event in options.events) newElement.addEventListener(event, options.events[event]);
			for (const key in options.style) newElement.style[key] = options.style[key];
			for (const key in options.dataset) if (typeof options.dataset[key] === "object") newElement.dataset[key] = JSON.stringify(options.dataset[key]);
			else newElement.dataset[key] = options.dataset[key].toString();
			return newElement;
		} else throw new Error("Invalid options provided to newElement.");
	}
	async function checkDevice() {
		await requireDOMInteractive();
		const innerWidth = window.innerWidth;
		mobile = innerWidth <= 600;
		tablet = innerWidth <= 1e3 && innerWidth >= 600;
		hasSidebar = innerWidth > 1e3;
		tabletHorizontal = tablet && innerWidth >= 784;
		tabletVertical = tablet && !tabletHorizontal;
		return {
			mobile,
			tablet,
			tabletHorizontal,
			tabletVertical,
			hasSidebar
		};
	}
	function isCustomEvent(event) {
		return event instanceof CustomEvent;
	}
	var EVENT_CHANNELS = function(EVENT_CHANNELS) {
		EVENT_CHANNELS["CHAT_MESSAGE"] = "chat-message";
		EVENT_CHANNELS["CHAT_NEW"] = "chat-box-new";
		EVENT_CHANNELS["CHAT_OPENED"] = "chat-box-opened";
		EVENT_CHANNELS["CHAT_PEOPLE_MENU_OPENED"] = "chat-people-menu-opened";
		EVENT_CHANNELS["CHAT_SETTINGS_MENU_OPENED"] = "chat-settings-menu-opened";
		EVENT_CHANNELS["CHAT_REFRESHED"] = "chat-refreshed";
		EVENT_CHANNELS["CHAT_RECONNECTED"] = "chat-reconnected";
		EVENT_CHANNELS["CHAT_CLOSED"] = "chat-closed";
		EVENT_CHANNELS["COMPANY_EMPLOYEES_PAGE"] = "company-employees-page";
		EVENT_CHANNELS["COMPANY_STOCK_PAGE"] = "company-stock-page";
		EVENT_CHANNELS["FACTION_ARMORY_TAB"] = "faction-armory-tab";
		EVENT_CHANNELS["FACTION_CRIMES"] = "faction-crimes";
		EVENT_CHANNELS["FACTION_CRIMES2"] = "faction-crimes2";
		EVENT_CHANNELS["FACTION_CRIMES2_TAB"] = "faction-crimes2-tab";
		EVENT_CHANNELS["FACTION_CRIMES2_REFRESH"] = "faction-crimes2-refresh";
		EVENT_CHANNELS["FACTION_GIVE_TO_USER"] = "faction-give-to-user";
		EVENT_CHANNELS["FACTION_UPGRADE_INFO"] = "faction-upgrade-info";
		EVENT_CHANNELS["FACTION_INFO"] = "faction-info";
		EVENT_CHANNELS["FACTION_MAIN"] = "faction-main";
		EVENT_CHANNELS["FACTION_NATIVE_FILTER"] = "faction-filter_native";
		EVENT_CHANNELS["FACTION_NATIVE_SORT"] = "faction-sort_native";
		EVENT_CHANNELS["FACTION_NATIVE_ICON_UPDATE"] = "faction-icon_update_native";
		EVENT_CHANNELS["FF_SCOUTER_GAUGE"] = "ff-scouter-gauge";
		EVENT_CHANNELS["ITEM_AMOUNT"] = "item-amount";
		EVENT_CHANNELS["ITEM_EQUIPPED"] = "item-equipped";
		EVENT_CHANNELS["ITEM_ITEMS_LOADED"] = "item-items-loaded";
		EVENT_CHANNELS["ITEM_SWITCH_TAB"] = "item-switch-tab";
		EVENT_CHANNELS["HOSPITAL_SWITCH_PAGE"] = "hospital-switch-page";
		EVENT_CHANNELS["JAIL_SWITCH_PAGE"] = "jail-switch-page";
		EVENT_CHANNELS["USERLIST_SWITCH_PAGE"] = "userlist-switch-page";
		EVENT_CHANNELS["TRAVEL_SELECT_TYPE"] = "travel-select-type";
		EVENT_CHANNELS["TRAVEL_SELECT_COUNTRY"] = "travel-select-country";
		EVENT_CHANNELS["TRAVEL_DESTINATION_UPDATE"] = "travel-destination-update";
		EVENT_CHANNELS["TRAVEL_ABROAD__SHOP_LOAD"] = "TRAVEL_ABROAD__SHOP_LOAD";
		EVENT_CHANNELS["TRAVEL_ABROAD__SHOP_REFRESH"] = "TRAVEL_ABROAD__SHOP_REFRESH";
		EVENT_CHANNELS["FEATURE_ENABLED"] = "feature-enabled";
		EVENT_CHANNELS["FEATURE_DISABLED"] = "feature-disabled";
		EVENT_CHANNELS["STATE_CHANGED"] = "state-changed";
		EVENT_CHANNELS["SHOP__LOAD"] = "SHOP__LOAD";
		EVENT_CHANNELS["GYM_LOAD"] = "gym-load";
		EVENT_CHANNELS["GYM_TRAIN"] = "gym-train";
		EVENT_CHANNELS["CRIMES_LOADED"] = "crimes-loaded";
		EVENT_CHANNELS["CRIMES_CRIME"] = "crimes-crime";
		EVENT_CHANNELS["CRIMES2_HOME_LOADED"] = "crimes2-home-loaded";
		EVENT_CHANNELS["CRIMES2_BURGLARY_LOADED"] = "crimes2-burglary-loaded";
		EVENT_CHANNELS["CRIMES2_CRIME_LOADED"] = "crimes2-crime-loaded";
		EVENT_CHANNELS["MISSION_LOAD"] = "mission-load";
		EVENT_CHANNELS["MISSION_REWARDS"] = "mission-rewards";
		EVENT_CHANNELS["TRADE"] = "trade";
		EVENT_CHANNELS["PROFILE_FETCHED"] = "profile-fetched";
		EVENT_CHANNELS["FILTER_APPLIED"] = "filter-applied";
		EVENT_CHANNELS["STATS_ESTIMATED"] = "stats-estimated";
		EVENT_CHANNELS["SWITCH_PAGE"] = "switch-page";
		EVENT_CHANNELS["AUCTION_SWITCH_TYPE"] = "auction-switch-type";
		EVENT_CHANNELS["ITEMMARKET_CATEGORY_ITEMS"] = "itemmarket-category-items";
		EVENT_CHANNELS["ITEMMARKET_CATEGORY_ITEMS_UPDATE"] = "itemmarket-category-items-update";
		EVENT_CHANNELS["ITEMMARKET_ITEMS"] = "itemmarket-items";
		EVENT_CHANNELS["ITEMMARKET_ITEMS_UPDATE"] = "itemmarket-items-update";
		EVENT_CHANNELS["ITEMMARKET_ITEM_DETAILS"] = "itemmarket-item-details";
		EVENT_CHANNELS["WINDOW__FOCUS"] = "WINDOW__FOCUS";
		EVENT_CHANNELS["PROPERTIES__ROUTE"] = "PROPERTIES__ROUTE";
		EVENT_CHANNELS["PROPERTIES__ROUTE_PAGE"] = "PROPERTIES__ROUTE_PAGE";
		EVENT_CHANNELS["EFFICIENT_REHAB"] = "EFFICIENT_REHAB";
		EVENT_CHANNELS["EFFICIENT_REHAB__INJECTED"] = "EFFICIENT_REHAB__INJECTED";
		EVENT_CHANNELS["CITY_ITEMS_MAP__SET_ITEMS"] = "CITY_ITEMS_MAP__SET_ITEMS";
		EVENT_CHANNELS["CITY_ITEMS_MAP__REQUEST_MODEL_ITEMS"] = "CITY_ITEMS_MAP__REQUEST_MODEL_ITEMS";
		EVENT_CHANNELS["CITY_ITEMS_MAP__MODEL_ITEMS"] = "CITY_ITEMS_MAP__MODEL_ITEMS";
		EVENT_CHANNELS["CITY_ITEMS_MAP__CLEAR"] = "CITY_ITEMS_MAP__CLEAR";
		return EVENT_CHANNELS;
	}({});
	var ANTI_SCRAPE_EVENTS = [
		"TRAVEL_ABROAD__SHOP_LOAD",
		"chat-message",
		"chat-box-opened",
		"chat-closed",
		"chat-refreshed",
		"chat-reconnected",
		"itemmarket-category-items",
		"itemmarket-category-items-update",
		"itemmarket-items",
		"itemmarket-items-update"
	];
	function triggerCustomListener(channel, payload) {
		if (ANTI_SCRAPE_EVENTS.includes(channel) && !isTabFocused()) return;
		EVENT_HANDLER.triggerEvent(channel, payload);
	}
	var EVENT_CHANNEL_XHR = "tt-xhr";
	function hasEventDetail(event) {
		return typeof event.detail !== "undefined";
	}
	function addXHRListener(callback) {
		SCRIPT_INJECTOR.injectXHR();
		window.addEventListener(EVENT_CHANNEL_XHR, (event) => {
			if (!hasEventDetail(event)) return;
			callback(event);
		});
	}
	var TornToolsCache = class {
		_cache;
		persistTimer = null;
		constructor() {
			this._cache = {};
		}
		set cache(value) {
			this._cache = value || {};
		}
		get cache() {
			return this._cache;
		}
		get(section, key) {
			return this.getCacheValue(section, key)?.value;
		}
		remove(section, key) {
			const actualKey = key ?? section;
			const actualSection = key ? section : null;
			if (actualSection && !this.hasValue(actualSection, actualKey) || !actualSection && !this.hasValue(actualKey.toString())) return;
			if (actualSection) delete this.cache[actualSection][actualKey];
			else delete this.cache[actualKey];
			this.schedulePersist();
		}
		hasValue(section, key) {
			return this.getCacheValue(section, key) !== null;
		}
		getCacheValue(section, key) {
			const actualKey = key ?? section;
			const actualSection = key ? section : null;
			let value = null;
			if (actualSection) {
				if (section in this.cache && actualKey in this.cache[actualSection]) value = this.cache[actualSection][actualKey];
			} else if (actualKey in this.cache) value = this.cache[actualKey];
			if (value === null || !("value" in value)) return null;
			if ("indefinite" in value) return value;
			else return value.timeout > Date.now() ? value : null;
		}
		set(object, ttl, section) {
			return this._set(object, ttl, section);
		}
		setIndefinite(object, section) {
			return this._set(object, null, section);
		}
		_set(object, ttl, section) {
			const timeout = ttl === null ? null : Date.now() + ttl;
			if (section) {
				if (!(section in this.cache)) this.cache[section] = {};
				for (const [key, value] of Object.entries(object)) this.cache[section][key] = this.createCacheValue(value, timeout);
			} else for (const [key, value] of Object.entries(object)) this.cache[key] = this.createCacheValue(value, timeout);
			this.schedulePersist();
		}
		createCacheValue(value, timeout) {
			if (timeout === null) return {
				value,
				indefinite: true
			};
			else return {
				value,
				timeout
			};
		}
		async clear(section) {
			if (section) {
				delete this.cache[section];
				this.schedulePersist();
			} else {
				this.cache = {};
				if (this.persistTimer) clearTimeout(this.persistTimer);
				this.persistTimer = null;
				await ttStorage.set({ cache: {} });
			}
		}
		async refresh() {
			let hasChanged = false;
			const now = Date.now();
			refreshObject(this.cache);
			for (const section in this.cache) if (!Object.keys(this.cache[section]).length) delete this.cache[section];
			if (hasChanged) this.schedulePersist();
			function refreshObject(object) {
				for (const key in object) {
					const value = object[key];
					if ("value" in value) {
						const cacheValue = value;
						if ("indefinite" in cacheValue || cacheValue.timeout > now) continue;
						hasChanged = true;
						delete object[key];
					} else refreshObject(value);
				}
			}
		}
		schedulePersist() {
			if (this.persistTimer) clearTimeout(this.persistTimer);
			this.persistTimer = setTimeout(() => {
				this.persistTimer = null;
				ttStorage.set({ cache: this.cache }).catch((err) => console.error("Failed to persist cache.", err));
			}, 500);
		}
	};
	var ttCache = new TornToolsCache();
	var DefaultSetting = class {
		type;
		defaultValue;
		constructor(type, defaultValue) {
			this.type = type;
			this.defaultValue = defaultValue ?? null;
		}
	};
	var DEFAULT_STORAGE = {
		version: {
			current: new DefaultSetting("string", () => RUNTIME_INFORMATION.getVersion()),
			initial: new DefaultSetting("string", () => RUNTIME_INFORMATION.getVersion()),
			oldVersion: new DefaultSetting("string"),
			showNotice: new DefaultSetting("boolean", true)
		},
		api: {
			torn: {
				key: new DefaultSetting("string"),
				online: new DefaultSetting("boolean", true),
				error: new DefaultSetting("string"),
				owner: new DefaultSetting("number")
			},
			tornstats: { key: new DefaultSetting("string") },
			yata: { key: new DefaultSetting("string") },
			ffScouter: { key: new DefaultSetting("string") }
		},
		settings: {
			updateNotice: new DefaultSetting("boolean", true),
			featureDisplay: new DefaultSetting("boolean", true),
			featureDisplayPosition: new DefaultSetting("string", "bottom-left"),
			featureDisplayOnlyFailed: new DefaultSetting("boolean", false),
			featureDisplayHideDisabled: new DefaultSetting("boolean", false),
			featureDisplayHideEmpty: new DefaultSetting("boolean", true),
			developer: new DefaultSetting("boolean", false),
			formatting: {
				tct: new DefaultSetting("boolean", false),
				date: new DefaultSetting("string", "eu"),
				time: new DefaultSetting("string", "eu")
			},
			sorting: { abroad: {
				column: new DefaultSetting("string", ""),
				order: new DefaultSetting("string", "none")
			} },
			notifications: {
				sound: new DefaultSetting("string", "default"),
				soundCustom: new DefaultSetting("string", ""),
				tts: new DefaultSetting("boolean", false),
				ttsVoice: new DefaultSetting("string", "default"),
				ttsRate: new DefaultSetting("number", 1),
				link: new DefaultSetting("boolean", true),
				volume: new DefaultSetting("number", 100),
				requireInteraction: new DefaultSetting("boolean", false),
				types: {
					global: new DefaultSetting("boolean", () => typeof Notification !== "undefined" && Notification.permission === "granted"),
					events: new DefaultSetting("boolean", true),
					messages: new DefaultSetting("boolean", true),
					status: new DefaultSetting("boolean", true),
					traveling: new DefaultSetting("boolean", true),
					cooldowns: new DefaultSetting("boolean", true),
					education: new DefaultSetting("boolean", true),
					newDay: new DefaultSetting("boolean", true),
					energy: new DefaultSetting("array", ["100%"]),
					nerve: new DefaultSetting("array", ["100%"]),
					happy: new DefaultSetting("array", ["100%"]),
					life: new DefaultSetting("array", ["100%"]),
					offline: new DefaultSetting("array", []),
					chainTimerEnabled: new DefaultSetting("boolean", true),
					chainBonusEnabled: new DefaultSetting("boolean", true),
					leavingHospitalEnabled: new DefaultSetting("boolean", true),
					landingEnabled: new DefaultSetting("boolean", true),
					cooldownDrugEnabled: new DefaultSetting("boolean", true),
					cooldownBoosterEnabled: new DefaultSetting("boolean", true),
					cooldownMedicalEnabled: new DefaultSetting("boolean", true),
					chainTimer: new DefaultSetting("array", []),
					chainBonus: new DefaultSetting("array", []),
					leavingHospital: new DefaultSetting("array", []),
					landing: new DefaultSetting("array", []),
					cooldownDrug: new DefaultSetting("array", []),
					cooldownBooster: new DefaultSetting("array", []),
					cooldownMedical: new DefaultSetting("array", []),
					stocks: new DefaultSetting("object", {}),
					missionsLimitEnabled: new DefaultSetting("boolean", false),
					missionsLimit: new DefaultSetting("string", ""),
					missionsExpireEnabled: new DefaultSetting("boolean", false),
					missionsExpire: new DefaultSetting("array", []),
					npcsGlobal: new DefaultSetting("boolean", true),
					npcs: new DefaultSetting("array", []),
					npcPlannedEnabled: new DefaultSetting("boolean", true),
					npcPlanned: new DefaultSetting("array", []),
					refillEnergyEnabled: new DefaultSetting("boolean", true),
					refillEnergy: new DefaultSetting("string", ""),
					refillNerveEnabled: new DefaultSetting("boolean", true),
					refillNerve: new DefaultSetting("string", "")
				}
			},
			apiUsage: {
				comment: new DefaultSetting("string", "TornTools"),
				delayEssential: new DefaultSetting("number", 30),
				delayBasic: new DefaultSetting("number", 120),
				delayPassive: new DefaultSetting("number", 3600),
				delayStakeouts: new DefaultSetting("number", 30),
				user: {
					bars: new DefaultSetting("boolean", true),
					cooldowns: new DefaultSetting("boolean", true),
					travel: new DefaultSetting("boolean", true),
					newevents: new DefaultSetting("boolean", true),
					newmessages: new DefaultSetting("boolean", true),
					refills: new DefaultSetting("boolean", true),
					stocks: new DefaultSetting("boolean", true),
					education: new DefaultSetting("boolean", true),
					networth: new DefaultSetting("boolean", true),
					inventory: new DefaultSetting("boolean", true),
					jobpoints: new DefaultSetting("boolean", true),
					merits: new DefaultSetting("boolean", true),
					perks: new DefaultSetting("boolean", true),
					icons: new DefaultSetting("boolean", true),
					ammo: new DefaultSetting("boolean", true),
					battlestats: new DefaultSetting("boolean", true),
					crimes: new DefaultSetting("boolean", true),
					workstats: new DefaultSetting("boolean", true),
					skills: new DefaultSetting("boolean", true),
					weaponexp: new DefaultSetting("boolean", true),
					properties: new DefaultSetting("boolean", true),
					calendar: new DefaultSetting("boolean", true),
					organizedcrime: new DefaultSetting("boolean", true),
					missions: new DefaultSetting("boolean", true),
					personalstats: new DefaultSetting("boolean", true),
					attacks: new DefaultSetting("boolean", true),
					money: new DefaultSetting("boolean", true),
					honors: new DefaultSetting("boolean", true),
					medals: new DefaultSetting("boolean", true),
					virus: new DefaultSetting("boolean", true)
				}
			},
			themes: {
				pages: new DefaultSetting("string", "default"),
				containers: new DefaultSetting("string", "default")
			},
			hideIcons: new DefaultSetting("array", []),
			hideCasinoGames: new DefaultSetting("array", []),
			hideStocks: new DefaultSetting("array", []),
			alliedFactions: new DefaultSetting("array", []),
			customLinks: new DefaultSetting("array", []),
			employeeInactivityWarning: new DefaultSetting("array", []),
			factionInactivityWarning: new DefaultSetting("array", []),
			userAlias: new DefaultSetting("array", []),
			csvDelimiter: new DefaultSetting("string", ";"),
			pages: {
				global: {
					alignLeft: new DefaultSetting("boolean", false),
					hideLevelUpgrade: new DefaultSetting("boolean", false),
					hideQuitButtons: new DefaultSetting("boolean", false),
					hideTutorials: new DefaultSetting("boolean", false),
					keepAttackHistory: new DefaultSetting("boolean", true),
					miniProfileLastAction: new DefaultSetting("boolean", true),
					reviveProvider: new DefaultSetting("string", ""),
					pageTitles: new DefaultSetting("boolean", true),
					stackingMode: new DefaultSetting("boolean", false),
					noOutsideLinkAlert: new DefaultSetting("boolean", false),
					urlFill: new DefaultSetting("boolean", true)
				},
				profile: {
					avgpersonalstats: new DefaultSetting("boolean", false),
					statusIndicator: new DefaultSetting("boolean", true),
					idBesideProfileName: new DefaultSetting("boolean", true),
					notes: new DefaultSetting("boolean", true),
					showAllyWarning: new DefaultSetting("boolean", true),
					ageToWords: new DefaultSetting("boolean", true),
					disableAllyAttacks: new DefaultSetting("boolean", true),
					box: new DefaultSetting("boolean", true),
					boxStats: new DefaultSetting("boolean", true),
					boxSpy: new DefaultSetting("boolean", true),
					boxStakeout: new DefaultSetting("boolean", true),
					boxAttackHistory: new DefaultSetting("boolean", true),
					boxFetch: new DefaultSetting("boolean", true)
				},
				chat: {
					fontSize: new DefaultSetting("number", 12),
					searchChat: new DefaultSetting("boolean", true),
					completeUsernames: new DefaultSetting("boolean", true),
					highlights: new DefaultSetting("array", [{
						name: "$player",
						color: "#7ca900"
					}]),
					titleHighlights: new DefaultSetting("array", []),
					tradeTimer: new DefaultSetting("boolean", true),
					resizable: new DefaultSetting("boolean", true),
					hideChatButton: new DefaultSetting("boolean", true),
					hideChat: new DefaultSetting("boolean", false)
				},
				sidebar: {
					notes: new DefaultSetting("boolean", true),
					highlightEnergy: new DefaultSetting("boolean", true),
					highlightNerve: new DefaultSetting("boolean", false),
					ocTimer: new DefaultSetting("boolean", true),
					oc2Timer: new DefaultSetting("boolean", true),
					oc2TimerPosition: new DefaultSetting("boolean", false),
					oc2TimerLevel: new DefaultSetting("boolean", true),
					factionOCTimer: new DefaultSetting("boolean", false),
					collapseAreas: new DefaultSetting("boolean", true),
					settingsLink: new DefaultSetting("boolean", true),
					hideGymHighlight: new DefaultSetting("boolean", false),
					hideNewspaperHighlight: new DefaultSetting("boolean", false),
					upkeepPropHighlight: new DefaultSetting("number", 0),
					barLinks: new DefaultSetting("boolean", true),
					pointsValue: new DefaultSetting("boolean", true),
					npcLootTimes: new DefaultSetting("boolean", true),
					npcLootTimesService: new DefaultSetting("string", "tornstats"),
					cooldownEndTimes: new DefaultSetting("boolean", true),
					companyAddictionLevel: new DefaultSetting("boolean", true),
					showJobPointsToolTip: new DefaultSetting("boolean", true),
					rwTimer: new DefaultSetting("boolean", true),
					virusTimer: new DefaultSetting("boolean", false)
				},
				popup: {
					dashboard: new DefaultSetting("boolean", true),
					marketSearch: new DefaultSetting("boolean", true),
					bazaarUsingExternal: new DefaultSetting("boolean", true),
					calculator: new DefaultSetting("boolean", true),
					stocksOverview: new DefaultSetting("boolean", true),
					notifications: new DefaultSetting("boolean", true),
					defaultTab: new DefaultSetting("string", "dashboard"),
					showStakeouts: new DefaultSetting("boolean", true),
					showIcons: new DefaultSetting("boolean", true),
					fullBarTime: new DefaultSetting("boolean", false)
				},
				icon: {
					global: new DefaultSetting("boolean", true),
					energy: new DefaultSetting("boolean", true),
					nerve: new DefaultSetting("boolean", true),
					happy: new DefaultSetting("boolean", true),
					life: new DefaultSetting("boolean", true),
					chain: new DefaultSetting("boolean", true),
					travel: new DefaultSetting("boolean", true)
				},
				education: {
					greyOut: new DefaultSetting("boolean", true),
					finishTime: new DefaultSetting("boolean", true)
				},
				jail: { filter: new DefaultSetting("boolean", true) },
				bank: {
					investmentInfo: new DefaultSetting("boolean", true),
					investmentDueTime: new DefaultSetting("boolean", true)
				},
				home: {
					networthDetails: new DefaultSetting("boolean", true),
					effectiveStats: new DefaultSetting("boolean", true)
				},
				items: {
					quickItems: new DefaultSetting("boolean", true),
					values: new DefaultSetting("boolean", true),
					drugDetails: new DefaultSetting("boolean", true),
					marketLinks: new DefaultSetting("boolean", false),
					highlightBloodBags: new DefaultSetting("string", "none"),
					missingFlowers: new DefaultSetting("boolean", false),
					missingPlushies: new DefaultSetting("boolean", false),
					missingBooks: new DefaultSetting("boolean", false),
					bookEffects: new DefaultSetting("boolean", true),
					canGains: new DefaultSetting("boolean", true),
					nerveGains: new DefaultSetting("boolean", true),
					candyHappyGains: new DefaultSetting("boolean", true),
					energyWarning: new DefaultSetting("boolean", true),
					medicalLife: new DefaultSetting("boolean", true),
					openedSupplyPackValue: new DefaultSetting("boolean", true),
					hideRecycleMessage: new DefaultSetting("boolean", false),
					hideTooManyItemsWarning: new DefaultSetting("boolean", false)
				},
				crimes: { quickCrimes: new DefaultSetting("boolean", true) },
				companies: {
					idBesideCompanyName: new DefaultSetting("boolean", false),
					specials: new DefaultSetting("boolean", true),
					autoStockFill: new DefaultSetting("boolean", true),
					employeeEffectiveness: new DefaultSetting("number", 18)
				},
				travel: {
					computer: new DefaultSetting("boolean", true),
					table: new DefaultSetting("boolean", true),
					cleanFlight: new DefaultSetting("boolean", false),
					tabTitleTimer: new DefaultSetting("boolean", false),
					travelProfits: new DefaultSetting("boolean", true),
					fillMax: new DefaultSetting("boolean", true),
					peopleFilter: new DefaultSetting("boolean", true),
					landingTime: new DefaultSetting("boolean", true),
					flyingTime: new DefaultSetting("boolean", true),
					itemFilter: new DefaultSetting("boolean", true),
					energyWarning: new DefaultSetting("boolean", true),
					cooldownWarnings: new DefaultSetting("boolean", true),
					autoTravelTableCountry: new DefaultSetting("boolean", false),
					autoFillMax: new DefaultSetting("boolean", true),
					efficientRehab: new DefaultSetting("boolean", true),
					efficientRehabSelect: new DefaultSetting("boolean", false),
					hideInventoryButton: new DefaultSetting("boolean", true),
					fastHunting: new DefaultSetting("boolean", true)
				},
				stocks: {
					filter: new DefaultSetting("boolean", true),
					acronyms: new DefaultSetting("boolean", true),
					valueAndProfit: new DefaultSetting("boolean", true),
					moneyInput: new DefaultSetting("boolean", true)
				},
				competitions: {
					easterEggs: new DefaultSetting("boolean", false),
					easterEggsAlert: new DefaultSetting("boolean", true)
				},
				events: { worth: new DefaultSetting("boolean", true) },
				hospital: { filter: new DefaultSetting("boolean", true) },
				auction: {
					filter: new DefaultSetting("boolean", true),
					movePagination: new DefaultSetting("boolean", false)
				},
				api: {
					autoFillKey: new DefaultSetting("boolean", true),
					autoDemo: new DefaultSetting("boolean", false),
					autoPretty: new DefaultSetting("boolean", true),
					clickableSelections: new DefaultSetting("boolean", true)
				},
				forums: {
					menu: new DefaultSetting("boolean", true),
					hidePosts: new DefaultSetting("object", {}),
					hideThreads: new DefaultSetting("object", {}),
					highlightPosts: new DefaultSetting("object", {}),
					highlightThreads: new DefaultSetting("object", {}),
					ignoredThreads: new DefaultSetting("object", {}),
					debugInfoBtn: new DefaultSetting("boolean", true),
					onlyNewFeedButton: new DefaultSetting("boolean", true)
				},
				bazaar: {
					itemsCost: new DefaultSetting("boolean", true),
					worth: new DefaultSetting("boolean", true),
					fillMax: new DefaultSetting("boolean", true),
					maxBuyIgnoreCash: new DefaultSetting("boolean", false),
					highlightSubVendorItems: new DefaultSetting("boolean", false)
				},
				trade: {
					itemValues: new DefaultSetting("boolean", true),
					openChat: new DefaultSetting("boolean", true)
				},
				displayCase: { worth: new DefaultSetting("boolean", true) },
				shops: {
					fillMax: new DefaultSetting("boolean", true),
					maxBuyIgnoreCash: new DefaultSetting("boolean", false),
					profit: new DefaultSetting("boolean", true),
					filters: new DefaultSetting("boolean", true),
					values: new DefaultSetting("boolean", true)
				},
				casino: {
					netTotal: new DefaultSetting("boolean", true),
					blackjack: new DefaultSetting("boolean", true),
					highlow: new DefaultSetting("boolean", false),
					highlowMovement: new DefaultSetting("boolean", true)
				},
				racing: {
					winPercentage: new DefaultSetting("boolean", true),
					upgrades: new DefaultSetting("boolean", true),
					filter: new DefaultSetting("boolean", true)
				},
				faction: {
					idBesideFactionName: new DefaultSetting("boolean", false),
					csvRaidReport: new DefaultSetting("boolean", true),
					csvRankedWarReport: new DefaultSetting("boolean", true),
					csvWarReport: new DefaultSetting("boolean", true),
					csvChainReport: new DefaultSetting("boolean", true),
					csvChallengeContributions: new DefaultSetting("boolean", true),
					openOc: new DefaultSetting("boolean", true),
					highlightOwn: new DefaultSetting("boolean", true),
					availablePlayers: new DefaultSetting("boolean", true),
					recommendedNnb: new DefaultSetting("boolean", true),
					ocNnb: new DefaultSetting("boolean", true),
					ocTimes: new DefaultSetting("boolean", true),
					ocLastAction: new DefaultSetting("boolean", true),
					banker: new DefaultSetting("boolean", true),
					showFullInfobox: new DefaultSetting("boolean", true),
					foldableInfobox: new DefaultSetting("boolean", true),
					numberMembers: new DefaultSetting("boolean", true),
					warFinishTimes: new DefaultSetting("boolean", false),
					memberFilter: new DefaultSetting("boolean", true),
					memberFilterRevivable: new DefaultSetting("boolean", false),
					armoryFilter: new DefaultSetting("boolean", true),
					armoryWorth: new DefaultSetting("boolean", true),
					upgradeRequiredRespect: new DefaultSetting("boolean", true),
					memberInfo: new DefaultSetting("boolean", false),
					rankedWarFilter: new DefaultSetting("boolean", true),
					quickItems: new DefaultSetting("boolean", true),
					stakeout: new DefaultSetting("boolean", true),
					showFactionSpy: new DefaultSetting("boolean", true),
					oc2Filter: new DefaultSetting("boolean", true),
					warnCrime: new DefaultSetting("boolean", false),
					rankedWarValue: new DefaultSetting("boolean", true),
					totalChallengeContributions: new DefaultSetting("boolean", true),
					memberRevives: new DefaultSetting("boolean", true),
					warReportHighlight: new DefaultSetting("boolean", true)
				},
				property: {
					filter: new DefaultSetting("boolean", true),
					value: new DefaultSetting("boolean", true),
					happy: new DefaultSetting("boolean", true)
				},
				gym: {
					specialist: new DefaultSetting("boolean", true),
					disableStats: new DefaultSetting("boolean", true),
					graph: new DefaultSetting("boolean", true),
					steadfast: new DefaultSetting("boolean", true),
					progress: new DefaultSetting("boolean", true)
				},
				missions: {
					hints: new DefaultSetting("boolean", true),
					rewards: new DefaultSetting("boolean", true)
				},
				attack: {
					bonusInformation: new DefaultSetting("boolean", true),
					timeoutWarning: new DefaultSetting("boolean", true),
					fairAttack: new DefaultSetting("boolean", true),
					weaponExperience: new DefaultSetting("boolean", true),
					hideAttackButtons: new DefaultSetting("array", [])
				},
				city: {
					items: new DefaultSetting("boolean", true),
					combineDuplicates: new DefaultSetting("boolean", true),
					groupByPeriod: new DefaultSetting("boolean", false),
					groupByPeriodUnit: new DefaultSetting("string", "day")
				},
				joblist: { specials: new DefaultSetting("boolean", true) },
				bounties: { filter: new DefaultSetting("boolean", true) },
				userlist: { filter: new DefaultSetting("boolean", true) },
				itemmarket: {
					highlightCheapItems: new DefaultSetting("number|empty", ""),
					highlightCheapItemsSound: new DefaultSetting("boolean", false),
					leftBar: new DefaultSetting("boolean", false),
					fillMax: new DefaultSetting("boolean", true),
					bazaars: new DefaultSetting("boolean", true)
				},
				competition: { filter: new DefaultSetting("boolean", true) },
				museum: { autoFill: new DefaultSetting("boolean", true) },
				enemies: { filter: new DefaultSetting("boolean", true) },
				friends: { filter: new DefaultSetting("boolean", true) },
				targets: { filter: new DefaultSetting("boolean", true) },
				crimes2: { value: new DefaultSetting("boolean", true) }
			},
			scripts: {
				noConfirm: {
					itemEquip: new DefaultSetting("boolean", true),
					tradeAccept: new DefaultSetting("boolean", false),
					pointsMarketRemove: new DefaultSetting("boolean", false),
					pointsMarketBuy: new DefaultSetting("boolean", false),
					abroadItemBuy: new DefaultSetting("boolean", true),
					propertiesSell: new DefaultSetting("boolean", false)
				},
				achievements: {
					show: new DefaultSetting("boolean", true),
					completed: new DefaultSetting("boolean", false)
				},
				reminders: {
					finished: new DefaultSetting("boolean", false),
					show: new DefaultSetting("boolean", true),
					types: {
						energyRefill: new DefaultSetting("boolean", true),
						nerveRefill: new DefaultSetting("boolean", true),
						medicalCooldown: new DefaultSetting("boolean", true),
						boosterCooldown: new DefaultSetting("boolean", true),
						drugCooldown: new DefaultSetting("boolean", true),
						bankInvestment: new DefaultSetting("boolean", true),
						virusCoding: new DefaultSetting("boolean", true),
						missionReward: new DefaultSetting("boolean", true),
						oc: new DefaultSetting("boolean", true),
						ocItem: new DefaultSetting("boolean", true),
						race: new DefaultSetting("boolean", true),
						education: new DefaultSetting("boolean", true)
					}
				},
				lastAction: {
					factionMember: new DefaultSetting("boolean", false),
					companyOwn: new DefaultSetting("boolean", false),
					companyOther: new DefaultSetting("boolean", false)
				},
				statsEstimate: {
					global: new DefaultSetting("boolean", true),
					delay: new DefaultSetting("number", 1500),
					cachedOnly: new DefaultSetting("boolean", true),
					displayNoResult: new DefaultSetting("boolean", false),
					maxLevel: new DefaultSetting("number", 100),
					profiles: new DefaultSetting("boolean", true),
					enemies: new DefaultSetting("boolean", true),
					hof: new DefaultSetting("boolean", true),
					attacks: new DefaultSetting("boolean", true),
					userlist: new DefaultSetting("boolean", true),
					bounties: new DefaultSetting("boolean", true),
					factions: new DefaultSetting("boolean", true),
					wars: new DefaultSetting("boolean", true),
					abroad: new DefaultSetting("boolean", true),
					competition: new DefaultSetting("boolean", true),
					rankedWars: new DefaultSetting("boolean", true),
					targets: new DefaultSetting("boolean", true)
				},
				ffScouter: {
					miniProfile: new DefaultSetting("boolean", true),
					profile: new DefaultSetting("boolean", true),
					attack: new DefaultSetting("boolean", true),
					factionList: new DefaultSetting("boolean", true),
					gauge: new DefaultSetting("boolean", true)
				}
			},
			external: {
				tornstats: new DefaultSetting("boolean", false),
				yata: new DefaultSetting("boolean", false),
				prometheus: new DefaultSetting("boolean", false),
				lzpt: new DefaultSetting("boolean", false),
				tornw3b: new DefaultSetting("boolean", false),
				ffScouter: new DefaultSetting("boolean", false),
				tornintel: new DefaultSetting("boolean", false),
				playgroundTorntools: new DefaultSetting("boolean", false)
			},
			servicePreferences: {
				spies: {
					tornstats: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 1)
					},
					yata: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 2)
					}
				},
				factionSpies: {
					tornstats: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 1)
					},
					yata: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 2)
					}
				},
				travelData: {
					prometheus: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 1)
					},
					tornintel: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 1)
					},
					yata: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 2)
					}
				}
			},
			reporting: {}
		},
		filters: {
			hospital: {
				enabled: new DefaultSetting("boolean", true),
				timeStart: new DefaultSetting("number", 0),
				timeEnd: new DefaultSetting("number", 100),
				levelStart: new DefaultSetting("number", 0),
				levelEnd: new DefaultSetting("number", 100),
				faction: new DefaultSetting("string", ""),
				activity: new DefaultSetting("array", []),
				revivesOn: new DefaultSetting("boolean", false)
			},
			jail: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				faction: new DefaultSetting("string", "All"),
				timeStart: new DefaultSetting("number", 0),
				timeEnd: new DefaultSetting("number", 100),
				levelStart: new DefaultSetting("number", 1),
				levelEnd: new DefaultSetting("number", 100),
				scoreStart: new DefaultSetting("number", 0),
				scoreEnd: new DefaultSetting("number", 5e3),
				bailCost: new DefaultSetting("number", -1)
			},
			racing: {
				enabled: new DefaultSetting("boolean", true),
				hideRaces: new DefaultSetting("array", []),
				timeStart: new DefaultSetting("number", 0),
				timeEnd: new DefaultSetting("number", 48),
				driversMin: new DefaultSetting("number", 2),
				driversMax: new DefaultSetting("number", 100),
				lapsMin: new DefaultSetting("number", 1),
				lapsMax: new DefaultSetting("number", 100),
				track: new DefaultSetting("array", []),
				name: new DefaultSetting("string", ""),
				exemptions: new DefaultSetting("array", [])
			},
			containers: new DefaultSetting("object", {}),
			travel: {
				open: new DefaultSetting("boolean", false),
				type: new DefaultSetting("string", "basic"),
				categories: new DefaultSetting("array", []),
				countries: new DefaultSetting("array", []),
				hideOutOfStock: new DefaultSetting("boolean", false),
				applySalesTax: new DefaultSetting("boolean", false),
				sellAnonymously: new DefaultSetting("boolean", false)
			},
			abroadPeople: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				status: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 0),
				levelEnd: new DefaultSetting("number", 100),
				faction: new DefaultSetting("string", ""),
				special: {
					newPlayer: new DefaultSetting("string", "both"),
					inCompany: new DefaultSetting("string", "both"),
					inFaction: new DefaultSetting("string", "both"),
					isDonator: new DefaultSetting("string", "both"),
					hasBounties: new DefaultSetting("string", "both"),
					bazaarOpen: new DefaultSetting("string", "both")
				},
				estimates: new DefaultSetting("array", []),
				ffScoreMax: new DefaultSetting("number", null),
				ffScoreMin: new DefaultSetting("number", null)
			},
			abroadItems: {
				enabled: new DefaultSetting("boolean", true),
				profitOnly: new DefaultSetting("boolean", false),
				outOfStock: new DefaultSetting("boolean", false),
				categories: new DefaultSetting("array", []),
				taxes: new DefaultSetting("array", [])
			},
			trade: { hideValues: new DefaultSetting("boolean", false) },
			gym: {
				specialist1: new DefaultSetting("string", "none"),
				specialist2: new DefaultSetting("string", "none"),
				strength: new DefaultSetting("boolean", false),
				speed: new DefaultSetting("boolean", false),
				defense: new DefaultSetting("boolean", false),
				dexterity: new DefaultSetting("boolean", false)
			},
			city: { highlightItems: new DefaultSetting("boolean", true) },
			bounties: {
				maxLevel: new DefaultSetting("number", 100),
				hideUnavailable: new DefaultSetting("boolean", false)
			},
			properties: {
				enabled: new DefaultSetting("boolean", true),
				daysOnLeaseLow: new DefaultSetting("number", 1),
				daysOnLeaseHigh: new DefaultSetting("number", 100),
				status: new DefaultSetting("string", "all"),
				types: new DefaultSetting("array", [])
			},
			userlist: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 0),
				levelEnd: new DefaultSetting("number", 100),
				special: {
					fedded: new DefaultSetting("string", "both"),
					fallen: new DefaultSetting("string", "both"),
					traveling: new DefaultSetting("string", "both"),
					newPlayer: new DefaultSetting("string", "both"),
					onWall: new DefaultSetting("string", "both"),
					inCompany: new DefaultSetting("string", "both"),
					inFaction: new DefaultSetting("string", "both"),
					isDonator: new DefaultSetting("string", "both"),
					inHospital: new DefaultSetting("string", "both"),
					inJail: new DefaultSetting("string", "both"),
					earlyDischarge: new DefaultSetting("string", "both"),
					hasBounties: new DefaultSetting("string", "both"),
					bazaarOpen: new DefaultSetting("string", "both")
				},
				hospReason: {
					attackedBy: new DefaultSetting("string", "both"),
					muggedBy: new DefaultSetting("string", "both"),
					hospitalizedBy: new DefaultSetting("string", "both"),
					other: new DefaultSetting("string", "both")
				},
				estimates: new DefaultSetting("array", []),
				ffScoreMax: new DefaultSetting("number", null),
				ffScoreMin: new DefaultSetting("number", null)
			},
			stocks: {
				enabled: new DefaultSetting("boolean", true),
				name: new DefaultSetting("string", ""),
				investment: {
					owned: new DefaultSetting("string", "both"),
					benefit: new DefaultSetting("string", "both"),
					passive: new DefaultSetting("string", "both"),
					collectionReady: new DefaultSetting("string", "both")
				},
				price: {
					price: new DefaultSetting("string", "both"),
					profit: new DefaultSetting("string", "both")
				}
			},
			faction: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 1),
				levelEnd: new DefaultSetting("number", 100),
				lastActionStart: new DefaultSetting("number", 0),
				lastActionEnd: new DefaultSetting("number", -1),
				status: new DefaultSetting("array", []),
				position: new DefaultSetting("string", ""),
				special: {
					fedded: new DefaultSetting("string", "both"),
					fallen: new DefaultSetting("string", "both"),
					newPlayer: new DefaultSetting("string", "both"),
					inCompany: new DefaultSetting("string", "both"),
					isDonator: new DefaultSetting("string", "both"),
					isRecruit: new DefaultSetting("string", "both")
				},
				ffScoreMax: new DefaultSetting("number", null),
				ffScoreMin: new DefaultSetting("number", null),
				revivable: new DefaultSetting("array", [])
			},
			factionArmory: {
				enabled: new DefaultSetting("boolean", true),
				hideUnavailable: new DefaultSetting("boolean", false),
				weapons: {
					name: new DefaultSetting("string", ""),
					category: new DefaultSetting("string", ""),
					rarity: new DefaultSetting("string", ""),
					weaponType: new DefaultSetting("string", ""),
					damage: new DefaultSetting("string", ""),
					accuracy: new DefaultSetting("string", ""),
					weaponBonus: new DefaultSetting("array", [])
				},
				armor: {
					name: new DefaultSetting("string", ""),
					rarity: new DefaultSetting("string", ""),
					defence: new DefaultSetting("string", ""),
					set: new DefaultSetting("string", ""),
					armorBonus: new DefaultSetting("string", "")
				},
				temporary: { name: new DefaultSetting("string", "") }
			},
			factionRankedWar: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				status: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 1),
				levelEnd: new DefaultSetting("number", 100),
				estimates: new DefaultSetting("array", []),
				ffScoreMax: new DefaultSetting("number", null),
				ffScoreMin: new DefaultSetting("number", null)
			},
			profile: {
				relative: new DefaultSetting("boolean", false),
				stats: new DefaultSetting("array", [])
			},
			competition: {
				levelStart: new DefaultSetting("number", 1),
				levelEnd: new DefaultSetting("number", 100),
				estimates: new DefaultSetting("array", [])
			},
			shops: {
				hideLoss: new DefaultSetting("boolean", false),
				hideUnder100: new DefaultSetting("boolean", false)
			},
			auction: {
				enabled: new DefaultSetting("boolean", true),
				weapons: {
					name: new DefaultSetting("string", ""),
					category: new DefaultSetting("string", ""),
					rarity: new DefaultSetting("string", ""),
					weaponType: new DefaultSetting("string", ""),
					damage: new DefaultSetting("string", ""),
					accuracy: new DefaultSetting("string", ""),
					weaponBonus: new DefaultSetting("array", []),
					quality: new DefaultSetting("string", "")
				},
				armor: {
					name: new DefaultSetting("string", ""),
					rarity: new DefaultSetting("string", ""),
					defence: new DefaultSetting("string", ""),
					set: new DefaultSetting("string", ""),
					armorBonus: new DefaultSetting("string", "")
				},
				items: {
					name: new DefaultSetting("string", ""),
					category: new DefaultSetting("string", ""),
					rarity: new DefaultSetting("string", "")
				}
			},
			enemies: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 0),
				levelEnd: new DefaultSetting("number", 100),
				estimates: new DefaultSetting("array", []),
				ffScoreMax: new DefaultSetting("number", null),
				ffScoreMin: new DefaultSetting("number", null)
			},
			friends: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 0),
				levelEnd: new DefaultSetting("number", 100)
			},
			targets: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 0),
				levelEnd: new DefaultSetting("number", 100),
				estimates: new DefaultSetting("array", []),
				ffScoreMax: new DefaultSetting("number", null),
				ffScoreMin: new DefaultSetting("number", null)
			},
			oc2: {
				enabled: new DefaultSetting("boolean", true),
				difficulty: new DefaultSetting("array", []),
				status: new DefaultSetting("array", [])
			}
		},
		userdata: new DefaultSetting("object", { date: -1 }),
		torndata: new DefaultSetting("object", { date: -2 }),
		stockdata: {
			date: new DefaultSetting("number", 0),
			stocks: new DefaultSetting("array", [])
		},
		factiondata: new DefaultSetting("object", {}),
		localdata: {
			tradeMessage: new DefaultSetting("number", 0),
			popup: { calculatorItems: new DefaultSetting("array", []) },
			vault: {
				initialized: new DefaultSetting("boolean", false),
				lastTransaction: new DefaultSetting("string", ""),
				total: new DefaultSetting("number", 0),
				user: {
					initial: new DefaultSetting("number", 0),
					current: new DefaultSetting("number", 0)
				},
				partner: {
					initial: new DefaultSetting("number", 0),
					current: new DefaultSetting("number", 0)
				}
			},
			chatResize: new DefaultSetting("object", {}),
			feedHidden: new DefaultSetting("object", {}),
			threadsHiddenInFeed: new DefaultSetting("array", []),
			hiddenTravelInventory: new DefaultSetting("boolean", false)
		},
		stakeouts: new DefaultSetting("object", {
			list: [],
			date: 0
		}),
		factionStakeouts: new DefaultSetting("object", {
			list: [],
			date: 0
		}),
		attackHistory: {
			fetchData: new DefaultSetting("boolean", true),
			lastAttack: new DefaultSetting("number", 0),
			history: new DefaultSetting("object", {})
		},
		notes: {
			sidebar: {
				text: new DefaultSetting("string", ""),
				height: new DefaultSetting("string", "22px")
			},
			profile: new DefaultSetting("object", {})
		},
		quick: {
			items: new DefaultSetting("array", []),
			factionItems: new DefaultSetting("array", []),
			crimes: new DefaultSetting("array", []),
			jail: new DefaultSetting("array", [])
		},
		cache: new DefaultSetting("object", {}),
		npcs: new DefaultSetting("object", {}),
		notificationHistory: new DefaultSetting("array", []),
		notifications: {
			events: new DefaultSetting("object", {}),
			messages: new DefaultSetting("object", {}),
			newDay: new DefaultSetting("object", {}),
			energy: new DefaultSetting("object", {}),
			happy: new DefaultSetting("object", {}),
			nerve: new DefaultSetting("object", {}),
			life: new DefaultSetting("object", {}),
			travel: new DefaultSetting("object", {}),
			drugs: new DefaultSetting("object", {}),
			boosters: new DefaultSetting("object", {}),
			medical: new DefaultSetting("object", {}),
			hospital: new DefaultSetting("object", {}),
			chain: new DefaultSetting("object", {}),
			chainCount: new DefaultSetting("object", {}),
			stakeouts: new DefaultSetting("object", {}),
			npcs: new DefaultSetting("object", {}),
			offline: new DefaultSetting("object", {}),
			missionsLimit: new DefaultSetting("object", {}),
			missionsExpire: new DefaultSetting("object", {}),
			refillEnergy: new DefaultSetting("object", {}),
			refillNerve: new DefaultSetting("object", {})
		},
		migrations: new DefaultSetting("array", [])
	};
	function getDefaultStorage(defaultStorage) {
		const newStorage = {};
		for (const key in defaultStorage) if (typeof defaultStorage[key] === "object") {
			const setting = defaultStorage[key];
			if (setting instanceof DefaultSetting && "defaultValue" in setting) switch (typeof setting.defaultValue) {
				case "function":
					newStorage[key] = setting.defaultValue();
					break;
				case "boolean":
				case "number":
				case "string":
				case "object":
					newStorage[key] = setting.defaultValue;
					break;
				default: newStorage[key] = setting.defaultValue;
			}
			else newStorage[key] = getDefaultStorage(defaultStorage[key]);
		} else newStorage[key] = defaultStorage[key];
		return newStorage;
	}
	var MIGRATIONS = [
		{
			id: "43fae1f2-5568-4ae5-b12f-f3625e1e58c6",
			version: "9.0.0",
			execute(database, _flags, _oldStorage) {
				database.cache["personal-stats"] = {};
			}
		},
		{
			id: "b194a6d5-4230-4b03-8a8b-bebd7c431cc9",
			version: "9.0.0",
			execute(database, _flags, _oldStorage) {
				database.settings.pages.api.autoDemo = false;
			}
		},
		{
			id: "b0f539ba-41f8-4eed-93e2-e8523f7c49a5",
			version: "9.0.1",
			execute(database, _flags, oldStorage) {
				const oldCustomLinks = oldStorage?.settings?.customLinks ?? [];
				database.settings.customLinks = oldCustomLinks.map((link) => {
					return link.preset && link.preset !== "custom" ? {
						newTab: link.newTab,
						location: link.location,
						name: link.name,
						preset: link.preset
					} : {
						newTab: link.newTab,
						location: link.location,
						name: link.name,
						href: link.href
					};
				});
			}
		},
		{
			id: "360b1f70-c78b-44c1-b217-24bd6b398bac",
			version: "9.0.5",
			execute(database, _flags, oldStorage) {
				if (!oldStorage?.settings?.userAlias || Array.isArray(oldStorage.settings.userAlias)) return;
				const oldUserAliases = oldStorage.settings.userAlias;
				database.settings.userAlias = Object.entries(oldUserAliases).map(([id, { alias, name }]) => {
					const idMatch = id.match(/^(\d+)$/);
					return idMatch ? {
						userId: parseInt(idMatch[0]),
						userName: name,
						alias
					} : {
						userId: -1,
						userName: name,
						alias,
						incorrectId: id
					};
				});
			}
		},
		{
			id: "95c020eb-2c75-4bbe-8fe9-64f96f108f48",
			version: "9.0.5",
			execute(database, _flags, oldStorage) {
				if (!oldStorage?.settings?.pages?.popup?.defaultTab) return;
				if (oldStorage.settings.pages.popup.defaultTab === "stocks") database.settings.pages.popup.defaultTab = "stocksOverview";
				else if (oldStorage.settings.pages.popup.defaultTab === "market") database.settings.pages.popup.defaultTab = "marketSearch";
			}
		},
		{
			id: "96356911-fecd-4b79-9825-ee5ad422c8fe",
			version: "9.0.5",
			execute(database, _flags, oldStorage) {
				if (typeof oldStorage?.settings?.pages?.popup.hoverBarTime !== "boolean") return;
				database.settings.pages.popup.fullBarTime = oldStorage.settings.pages.popup.hoverBarTime;
			}
		},
		{
			id: "7396191c-35a9-4d92-905a-0e411f9a6823",
			version: "9.0.5",
			execute(_database, _flags, _oldStorage) {
				ttStorage.remove("usage");
			}
		},
		{
			id: "d3e6e03a-698d-4df4-9062-4d3c9ce9d479",
			version: "9.0.5",
			execute(database, _flags, oldStorage) {
				if (!oldStorage?.filters?.travel?.categories?.includes("other")) return;
				database.filters.travel.categories = [...oldStorage.filters.travel.categories, "defensive"];
			}
		},
		{
			id: "700848e9-ee48-42ce-b8b1-893cb471cfe4",
			version: "9.0.6",
			execute(_database, flags, _oldStorage) {
				flags.clearCache = true;
			}
		},
		{
			id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
			version: "9.0.6",
			execute(database, _flags, oldStorage) {
				const oldStakeouts = oldStorage?.stakeouts;
				if (!oldStakeouts || typeof oldStakeouts !== "object") return;
				const reservedKeys = new Set([
					"order",
					"date",
					"list"
				]);
				const oldOrder = oldStakeouts.order ?? [];
				const list = [];
				Object.entries(oldStakeouts).filter((entry) => !reservedKeys.has(entry[0])).forEach(([id, data]) => {
					const orderIndex = oldOrder.indexOf(id);
					list.push({
						...data,
						id: parseInt(id),
						order: orderIndex !== -1 ? orderIndex : Date.now()
					});
				});
				database.stakeouts.list = list;
			}
		},
		{
			id: "b2c3d4e5-f6a7-8901-bcde-f12345678901",
			version: "9.0.6",
			execute(database, _flags, oldStorage) {
				const oldFactionStakeouts = oldStorage?.factionStakeouts;
				if (!oldFactionStakeouts || typeof oldFactionStakeouts !== "object") return;
				const reservedKeys = new Set(["date", "list"]);
				const list = [];
				Object.entries(oldFactionStakeouts).filter((entry) => !reservedKeys.has(entry[0])).forEach(([id, data]) => {
					list.push({
						...data,
						id: parseInt(id),
						order: Date.now()
					});
				});
				database.factionStakeouts.list = list;
			}
		},
		{
			id: "16d7de5c-e9ad-4060-966e-49b4252301c5",
			version: "9.0.7",
			execute(_database, _flags, _oldStorage) {
				OFFLOAD_SERVICE.reinitializeTimers().catch(() => {});
			}
		},
		{
			id: "a1b8db49-f255-43fc-b3b8-dc82b8c072b1",
			version: "9.0.9",
			execute(database, _flags, oldStorage) {
				const owner = oldStorage.userdata?.profile?.id;
				if (!owner) return;
				database.api.torn.owner = owner;
			}
		},
		{
			id: "8a88db28-d02c-4b08-a672-bb73394b5ae4",
			version: "9.0.12",
			execute(_database, _flags, _oldStorage) {
				OFFLOAD_SERVICE.reinitializeTimers().catch(() => {});
			}
		},
		{
			id: "19384047-faaa-4894-a0bb-1695b964a125",
			version: "9.0.14",
			execute(_database, flags, _oldStorage) {
				flags.updateStockdata = true;
			}
		},
		{
			id: "b2102994-0920-4586-8259-0e5beedc7f13",
			version: "9.1.0",
			execute(database, _flags, oldStorage) {
				if (oldStorage.api.torn.owner) return;
				const owner = oldStorage.userdata?.profile?.id;
				if (!owner) return;
				database.api.torn.owner = owner;
			}
		},
		{
			id: "8f883a44-fa45-407b-bdc7-18c6982ab108",
			version: "9.1.0",
			execute(database, _flags, _oldStorage) {
				database.cache["stats-estimate"] = {};
			}
		},
		{
			id: "4b9a96e4-ce68-46c1-b5ab-b7dfbc699db0",
			version: "9.1.2",
			execute(_database, flags, _oldStorage) {
				flags.updateUserdata = true;
			}
		}
	];
	async function executeMigrationScripts(storage, oldStorage) {
		if (RUNTIME_INFORMATION.isUserscript()) return;
		const migrations = MIGRATIONS.filter(({ version }) => toNumericVersion(version) >= toNumericVersion(storage.version.initial)).filter(({ id }) => !storage.migrations.map(({ id }) => id).includes(id));
		const flags = {
			updateUserdata: false,
			updateFactiondata: false,
			updateStockdata: false,
			updateTorndata: false,
			clearCache: false
		};
		migrations.reverse().forEach((migration) => {
			migration.execute(storage, flags, oldStorage);
			storage.migrations.push({ id: migration.id });
		});
		if (flags.updateUserdata) storage.userdata.date = 0;
		if (flags.updateFactiondata) storage.factiondata.date = 0;
		if (flags.updateStockdata) storage.stockdata.date = 0;
		if (flags.updateTorndata) storage.torndata.date = 0;
		if (flags.clearCache) storage.cache = {};
	}
	var settings;
	var filters;
	var version;
	var api;
	var userdata;
	var torndata;
	var stakeouts;
	var attackHistory;
	var notes;
	var factiondata;
	var quick;
	var localdata;
	var npcs;
	var notificationHistory;
	var stockdata;
	var factionStakeouts;
	var notifications;
	var migrations;
	var storageListeners = {
		settings: [],
		filters: [],
		version: [],
		userdata: [],
		torndata: [],
		attackHistory: [],
		stakeouts: [],
		factionStakeouts: [],
		notes: [],
		factiondata: [],
		localdata: [],
		cache: [],
		api: [],
		npcs: [],
		stockdata: [],
		notificationHistory: [],
		notifications: [],
		quick: [],
		migrations: []
	};
	var databaseLoaded = false;
	var databaseLoadPromise = null;
	async function loadDatabase(force = false) {
		if (databaseLoaded && !force) return {
			settings,
			filters,
			version,
			userdata,
			stakeouts,
			factionStakeouts,
			notes,
			factiondata,
			localdata,
			cache: ttCache.cache,
			api,
			npcs,
			torndata,
			notificationHistory,
			attackHistory,
			quick,
			stockdata,
			notifications,
			migrations
		};
		if (databaseLoadPromise) return await databaseLoadPromise;
		databaseLoadPromise = (async () => {
			const database = await ttStorage.get();
			populateDatabaseVariables(database);
			console.debug("TT - Database loaded.");
			return database;
		})();
		try {
			const result = await databaseLoadPromise;
			databaseLoaded = true;
			databaseLoadPromise = null;
			return result;
		} catch (error) {
			databaseLoadPromise = null;
			throw error;
		}
	}
	async function migrateDatabase(force = false) {
		try {
			const loadedStorage = await ttStorage.get();
			if (!loadedStorage || !Object.keys(loadedStorage).length) {
				console.log("TT - Fresh installation detected, setting up default storage.");
				await ttStorage.reset();
				await loadDatabase();
				return;
			}
			const storedVersion = loadedStorage?.version?.current || "5.0.0";
			const currentVersion = RUNTIME_INFORMATION.getVersion();
			console.log(`TT - Migration check: ${storedVersion} -> ${currentVersion}`);
			const migratedStorage = convertStorage(loadedStorage, DEFAULT_STORAGE);
			await executeMigrationScripts(migratedStorage, loadedStorage);
			migratedStorage.version.current = currentVersion;
			await ttStorage.set(migratedStorage);
			populateDatabaseVariables(migratedStorage);
			console.debug("TT - Database migration completed successfully.");
		} catch (error) {
			console.error("TT - Database migration failed:", error);
			await loadDatabase();
		}
	}
	function convertStorage(oldStorage, defaultStorage) {
		const newStorage = {};
		for (const key in defaultStorage) {
			if (!oldStorage) oldStorage = {};
			const defaultValue = defaultStorage[key];
			const oldValue = key in oldStorage ? oldStorage[key] : void 0;
			if (typeof defaultValue === "object" && defaultValue !== null) {
				if (defaultValue instanceof DefaultSetting) newStorage[key] = migrateDefaultSetting(oldValue ?? {}, defaultValue);
				else newStorage[key] = convertStorage(oldValue ?? {}, defaultValue);
			} else newStorage[key] = oldValue ?? defaultValue;
		}
		return newStorage;
	}
	function migrateDefaultSetting(oldValue, setting) {
		if (isValidSettingValue(oldValue, setting)) return oldValue;
		if (setting.defaultValue !== null) return typeof setting.defaultValue === "function" ? setting.defaultValue() : setting.defaultValue;
		return null;
	}
	function isValidSettingValue(value, setting) {
		if (setting.type === "array") return Array.isArray(value);
		return setting.type.split("|").some((type) => type === "empty" && value === "" || typeof value === type);
	}
	function populateDatabaseVariables(database) {
		settings = database.settings;
		filters = database.filters;
		version = database.version;
		api = database.api;
		userdata = database.userdata;
		torndata = database.torndata;
		localdata = database.localdata;
		stakeouts = database.stakeouts;
		attackHistory = database.attackHistory;
		notes = database.notes;
		factiondata = database.factiondata;
		quick = database.quick;
		npcs = database.npcs;
		stockdata = database.stockdata;
		factionStakeouts = database.factionStakeouts;
		notificationHistory = database.notificationHistory;
		notifications = database.notifications;
		migrations = database.migrations;
		ttCache.cache = database.cache;
	}
	var initializedDatabaseListeners = false;
	function initializeDatabaseListener() {
		if (initializedDatabaseListeners) return;
		RUNTIME_STORAGE.addChangeListener((changes, area) => {
			if (area === "local") for (const key in changes) {
				switch (key) {
					case "settings":
						settings = changes.settings.newValue;
						break;
					case "filters":
						filters = changes.filters.newValue;
						break;
					case "version":
						version = changes.version.newValue;
						break;
					case "userdata":
						userdata = changes.userdata.newValue;
						break;
					case "api":
						api = changes.api.newValue;
						break;
					case "torndata":
						torndata = changes.torndata.newValue;
						break;
					case "stakeouts":
						stakeouts = changes.stakeouts.newValue;
						break;
					case "attackHistory":
						attackHistory = changes.attackHistory.newValue;
						break;
					case "notes":
						notes = changes.notes.newValue;
						break;
					case "factiondata":
						factiondata = changes.factiondata.newValue;
						break;
					case "quick":
						quick = changes.quick.newValue;
						break;
					case "localdata":
						localdata = changes.localdata.newValue;
						break;
					case "cache":
						ttCache.cache = changes.cache.newValue;
						break;
					case "npcs":
						npcs = changes.npcs.newValue;
						break;
					case "stockdata":
						stockdata = changes.stockdata.newValue;
						break;
					case "notificationHistory":
						notificationHistory = changes.notificationHistory.newValue;
						break;
					case "notifications":
						notifications = changes.notifications.newValue;
						break;
					case "factionStakeouts": factionStakeouts = changes.factionStakeouts.newValue;
				}
				if (storageListeners[key]) storageListeners[key].forEach((listener) => listener(changes[key].oldValue, changes[key].newValue));
			}
		});
		initializedDatabaseListeners = true;
	}
	function setLocaldata(data) {
		localdata = data;
	}
	function setFilters(data) {
		filters = data;
	}
	var REGEXES = {
		convertToNumber: /-?[\d,]+(\.\d+)?/,
		formatNumber: /\B(?=(\d{3})+(?!\d))/g
	};
	function convertToNumber(string) {
		if (!string) return NaN;
		const match = string.match(REGEXES.convertToNumber);
		return match ? Number(match[0].replaceAll(",", "")) : NaN;
	}
	function formatNumber(number, partialOptions = {}) {
		const options = {
			shorten: false,
			formatter: void 0,
			decimals: 0,
			currency: false,
			forceOperation: false,
			roman: false,
			...partialOptions
		};
		if (typeof number !== "number") {
			if (Number.isNaN(parseInt(number))) return number;
			else number = parseFloat(number);
		}
		if (number === Number.POSITIVE_INFINITY) return "∞";
		if (options.decimals !== void 0) number = parseFloat(number.toFixed(options.decimals));
		if (options.formatter) return options.formatter.format(number);
		if (options.roman) {
			if (number === 0) return "";
			else if (number < 0) throw "Roman numbers can only be positive!";
			const ROMAN = [
				[1e3, "M"],
				[900, "CM"],
				[500, "D"],
				[400, "CD"],
				[100, "C"],
				[90, "XC"],
				[50, "L"],
				[40, "XL"],
				[10, "X"],
				[9, "IX"],
				[5, "V"],
				[4, "IV"],
				[1, "I"]
			];
			return toRoman(number);
			function toRoman(number) {
				if (number === 0) return "";
				for (const [value, character] of ROMAN) {
					if (number < value) continue;
					return character + toRoman(number - value);
				}
				return "N/A";
			}
		}
		const abstract = Math.abs(number);
		const operation = number < 0 ? "-" : options.forceOperation ? "+" : "";
		let text;
		if (options.shorten) {
			const version = options.shorten === true ? 1 : options.shorten;
			const decimals = options.decimals !== -1 ? options.decimals : 3;
			const words = (() => {
				switch (version) {
					case 1: return {
						thousand: "k",
						million: "mil",
						billion: "bill"
					};
					case 2:
					case 3: return {
						thousand: "k",
						million: "m",
						billion: "b"
					};
				}
			})();
			if (version === 1 || version === 2) {
				if (abstract >= 1e9) {
					if (abstract % 1e9 === 0) text = (abstract / 1e9).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") + words.billion;
					else text = (abstract / 1e9).toFixed(3) + words.billion;
				} else if (abstract >= 1e6) {
					if (abstract % 1e6 === 0) text = abstract / 1e6 + words.million;
					else text = (abstract / 1e6).toFixed(3) + words.million;
				} else if (abstract >= 1e3) {
					if (abstract % 1e3 === 0) text = abstract / 1e3 + words.thousand;
				}
			} else if (abstract >= 1e9) {
				if (abstract % 1e9 === 0) text = abstract / 1e9 + words.billion;
				else text = parseFloat((abstract / 1e9).toFixed(decimals)) + words.billion;
			} else if (abstract >= 1e6) {
				if (abstract % 1e6 === 0) text = abstract / 1e6 + words.million;
				else text = parseFloat((abstract / 1e6).toFixed(decimals)) + words.million;
			} else if (abstract >= 1e3) {
				if (abstract % 1e3 === 0) text = abstract / 1e3 + words.thousand;
				else if (abstract % 100 === 0) text = abstract / 1e3 + words.thousand;
			}
		}
		if (!text) text = abstract.toString().replace(REGEXES.formatNumber, ",");
		return `${operation}${options.currency ? "$" : ""}${text}`;
	}
	function capitalizeText(text, partialOptions = {}) {
		if (!{
			everyWord: false,
			...partialOptions
		}.everyWord) return text[0].toUpperCase() + text.slice(1);
		return text.trim().split(" ").map((word) => capitalizeText(word)).join(" ").trim();
	}
	var LINKS = {
		auction: "https://www.torn.com/amarket.php",
		bank: "https://www.torn.com/bank.php",
		bazaar: "https://www.torn.com/bazaar.php",
		bounties: "https://www.torn.com/bounties.php#!p=main",
		chain: "https://www.torn.com/factions.php?step=your#/war/chain",
		church: "https://www.torn.com/church.php",
		committee: "https://www.torn.com/committee.php",
		companies: "https://www.torn.com/companies.php",
		companyEmployees: "https://www.torn.com/companies.php#/option=employees",
		crimes: "https://www.torn.com/crimes.php",
		donator: "https://www.torn.com/donator.php",
		education: "https://www.torn.com/page.php?sid=education",
		events: "https://www.torn.com/events.php#/step=all",
		faction: "https://www.torn.com/factions.php",
		faction__ranked_war: "https://www.torn.com/factions.php?step=your&type=1#/war/rank",
		faction_oc: "https://www.torn.com/factions.php?step=your#/tab=crimes",
		gym: "https://www.torn.com/gym.php",
		home: "https://www.torn.com/index.php",
		homepage: "https://www.torn.com/index.php",
		hospital: "https://www.torn.com/hospitalview.php",
		itemmarket: "https://www.torn.com/page.php?sid=ItemMarket",
		items: "https://www.torn.com/item.php",
		items_booster: "https://www.torn.com/item.php#boosters-items",
		items_candy: "https://www.torn.com/item.php#candy-items",
		items_drug: "https://www.torn.com/item.php#drugs-items",
		items_medical: "https://www.torn.com/item.php#medical-items",
		jailview: "https://www.torn.com/jailview.php",
		jobs: "https://www.torn.com/companies.php",
		loan: "https://www.torn.com/loan.php",
		messages: "https://www.torn.com/messages.php",
		missions: "https://www.torn.com/page.php?sid=missions",
		organizedCrimes: "https://www.torn.com/factions.php?step=your#/tab=crimes",
		pc: "https://www.torn.com/pc.php",
		points: "https://www.torn.com/page.php?sid=points",
		pointsmarket: "https://www.torn.com/pmarket.php",
		properties: "https://www.torn.com/properties.php",
		property_upkeep: "https://www.torn.com/properties.php#/p=options&tab=upkeep",
		property_vault: "https://www.torn.com/properties.php#/p=options&tab=vault",
		raceway: "https://www.torn.com/page.php?sid=racing",
		staff: "https://www.torn.com/staff.php",
		stocks: "https://www.torn.com/page.php?sid=stocks",
		trade: "https://www.torn.com/trade.php",
		travelagency: "https://www.torn.com/page.php?sid=travel"
	};
	LINKS.donator, LINKS.donator, LINKS.staff, LINKS.committee, LINKS.church, LINKS.jobs, LINKS.jobs, LINKS.jobs, LINKS.jobs, LINKS.jobs, LINKS.jobs, LINKS.companies, LINKS.companies, LINKS.companies, LINKS.faction, LINKS.faction, LINKS.faction, LINKS.faction, LINKS.faction, LINKS.education, LINKS.education, LINKS.bank, LINKS.bank, LINKS.travelagency, LINKS.property_vault, LINKS.loan, LINKS.auction, LINKS.bazaar, LINKS.itemmarket, LINKS.pointsmarket, LINKS.stocks, LINKS.stocks, LINKS.trade, LINKS.homepage, LINKS.raceway, LINKS.raceway, LINKS.faction_oc, LINKS.faction_oc, LINKS.faction_oc, LINKS.faction_oc, LINKS.bounties, LINKS.bank, LINKS.auction, LINKS.auction, LINKS.hospital, LINKS.hospital, LINKS.hospital, LINKS.jailview, LINKS.hospital, LINKS.items_booster, LINKS.items_booster, LINKS.items_booster, LINKS.items_booster, LINKS.items_booster, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_drug, LINKS.items_drug, LINKS.items_drug, LINKS.items_drug, LINKS.items_drug, LINKS.travelagency, LINKS.travelagency, LINKS.travelagency, LINKS.travelagency, LINKS.travelagency, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.property_upkeep, LINKS.property_upkeep, LINKS.property_upkeep;
	[
		{
			id: 1,
			reason: "Admin"
		},
		{
			id: 4,
			reason: "NPC"
		},
		{
			id: 7,
			reason: "NPC"
		},
		{
			id: 9,
			reason: "NPC"
		},
		{
			id: 10,
			reason: "NPC"
		},
		{
			id: 15,
			reason: "NPC"
		},
		{
			id: 17,
			reason: "NPC"
		},
		{
			id: 19,
			reason: "NPC"
		},
		{
			id: 20,
			reason: "NPC"
		},
		{
			id: 21,
			reason: "NPC"
		}
	].map(({ id }) => id);
	function getRFC() {
		const rfc = getCookie("rfc_v");
		if (!rfc) for (const cookie of document.cookie.split("; ")) {
			const parts = cookie.split("=");
			if (parts[0] === "rfc_v") return parts[1];
		}
		return rfc;
	}
	function millisToNewDay() {
		const now = Date.now();
		const newDate = new Date();
		newDate.setUTCHours(0, 0, 0);
		newDate.setUTCDate(newDate.getUTCDate() + 1);
		return newDate.getTime() - now;
	}
	function isUseItem(step, _json) {
		return step === "useItem";
	}
	var Feature = class {
		name;
		scope;
		executionTiming;
		constructor(name, scope, executionTiming = "CONTENT_LOADED") {
			this.name = name;
			this.scope = scope;
			this.executionTiming = executionTiming;
		}
		precondition() {
			return true;
		}
		initialise() {}
		execute(liveReload) {}
		cleanup() {}
		storageKeys() {
			return [];
		}
		requirements() {
			return true;
		}
		shouldTriggerEvents() {
			return false;
		}
		shouldLiveReload() {
			return false;
		}
		requiresScreenInformation() {
			return true;
		}
	};
	var SUPPLY_PACK_ITEMS = [
		364,
		365,
		370,
		588,
		815,
		817,
		818,
		1035,
		1057,
		1078,
		1079,
		1080,
		1081,
		1082,
		1083,
		1112,
		1113,
		1114,
		1115,
		1116,
		1117,
		1118,
		1119,
		1120,
		1121,
		1122,
		1293,
		1298
	];
	function addListener() {
		let reqXID;
		let itemID;
		addXHRListener(async ({ detail: { xhr, json } }) => {
			if (!FEATURE_MANAGER.isEnabled(OpenedSupplyPackValueFeature)) return;
			const params = new URLSearchParams(xhr.requestBody);
			if (!isUseItem(params.get("step"), json) || !json.success) return;
			if (json?.itemID) itemID = parseInt(json.itemID);
			else if (params.has("id")) itemID = convertToNumber(params.get("id"));
			else if (params.has("itemID")) itemID = convertToNumber(params.get("itemID"));
			if (shouldDisplayOpenedValue(itemID)) reqXID = (await requireElement(`[data-item="${itemID}"] .pack-open-msg input[type="hidden"]`)).value;
			if ((params.get("XID") === reqXID || isDrugPackUseRequest(params) || SUPPLY_PACK_ITEMS.includes(itemID)) && json.items?.itemAppear) await showTotalValue(calculateValueFromResponse(json), itemID);
		});
	}
	function calculateValueFromResponse(response) {
		if (!response.items?.itemAppear) return null;
		return response.items.itemAppear.map((item) => "isMoney" in item ? convertToNumber(item.moneyGain.substring(1)) : ITEM_RESOLVER.getFullItem(parseInt(item.ID)).value.market_price * parseInt(item.qty)).reduce((totalValue, value) => totalValue + value, 0);
	}
	async function showTotalValue(totalOpenedValue, itemID) {
		await sleep(.5 * TO_MILLIS.SECONDS);
		const greenMsg = await requireElement(`[data-item="${itemID}"] .cont-wrap form p`);
		removeTotalValueElement();
		const openedValueTextElement = elementBuilder({
			id: "ttOpenedValueText",
			type: "strong",
			class: "tt-opened-supply-pack-value-text",
			text: `TornTools total value: ${formatNumber(totalOpenedValue, { currency: true })}`
		});
		greenMsg.insertAdjacentElement("beforeend", openedValueTextElement);
	}
	function shouldDisplayOpenedValue(itemID) {
		return SUPPLY_PACK_ITEMS.includes(itemID) && !isDrugPack(itemID);
	}
	function isDrugPack(itemID) {
		return itemID === 370;
	}
	function isDrugPackUseRequest(params) {
		return convertToNumber(params.get("item")) === 370 || convertToNumber(params.get("itemID")) === 370;
	}
	function removeTotalValueElement() {
		document.getElementById("ttOpenedValueText")?.remove();
	}
	var OpenedSupplyPackValueFeature = class extends Feature {
		constructor() {
			super("Opened Supply Pack Value", "items");
		}
		isEnabled() {
			return settings.pages.items.openedSupplyPackValue;
		}
		initialise() {
			addListener();
		}
		cleanup() {
			removeTotalValueElement();
		}
		storageKeys() {
			return ["settings.pages.items.openedSupplyPackValue"];
		}
		async requirements() {
			if (!ITEM_RESOLVER.hasFullItems()) return "No API access.";
			return true;
		}
	};
	_css(".tt-hidden{display:none!important}.tt-black-overlay{z-index:100;background-color:#00000059;width:100%;height:100%;position:fixed;top:0;left:0}.no-margin{margin:0}.tt-delimiter{border-top:#ccc;border-left:none;border-right:none;border-top:1px solid var(--sidebar-horizontal-divider-bg-color);border-bottom:#fff;border-bottom:1px solid var(--sidebar-horizontal-divider-shadow-color);height:0;margin-bottom:5px;overflow:hidden}.tt-overlay{z-index:1000000;background-color:#00000059;width:100%;height:100%;position:fixed;top:0;left:0}.tt-overlay-item,.tt-overlay-item-notbroken{z-index:999999999;position:relative}.tt-overlay-item .tt-overlay-ignore{z-index:0;pointer-events:none}.tt-overlay-item .tt-overlay-ignore:before{content:\"\";z-index:1000000;background-color:#00000059;width:100%;height:100%;position:absolute;top:0;left:0}.relative{position:relative}.flex-break{border:0;height:0;margin:0;flex-basis:100%!important}.mt10{margin-top:10px}.mb10{margin-bottom:10px}.t-flex{display:flex}[class*=torn-icon-]{vertical-align:middle;background:url(https://www.torn.com/images/v2/city/location_icons_34x34px.svg) no-repeat;width:34px;height:34px;display:inline-block}.torn-icon-item-market{background-position:-68px -34px}.tt-sidebar-area{margin-top:2px;overflow:hidden}.tt-sidebar-area>div{cursor:pointer;vertical-align:top;background-color:var(--default-bg-panel-color);border-top-right-radius:5px;border-bottom-right-radius:5px;position:relative;overflow:hidden}.tt-sidebar-area a{color:var(--default-content-font-color);justify-content:flex-start;align-items:center;height:100%;text-decoration:none;display:flex;overflow:hidden}.tt-sidebar-area a span{float:none;vertical-align:middle;margin-left:10px;display:inline-block}.tt-button-link{cursor:pointer;color:var(--default-blue-color)}.tt-btn{background-color:var(--tt-color-light-green);color:#000;border-radius:6px;width:fit-content}.tt-btn:not([disabled]){cursor:pointer}.tt-btn[disabled]{cursor:not-allowed;opacity:.4}.tt-msg-box{background:var(--info-msg-grey-gradient);box-shadow:var(--info-msg-box-shadow);color:var(--info-msg-font-color);border-radius:5px;margin-top:10px;font-size:0;line-height:16px}.tt-msg-box .tt-msg-div{background:var(--info-msg-horizontal-gradient);border-radius:5px;justify-content:flex-start;display:flex}.tt-msg-box .tt-msg{vertical-align:middle;background-color:var(--default-bg-panel-active-color);background:var(--info-msg-delimiter-gradient);border-radius:0 5px 5px 0;flex-grow:1;width:1px;height:auto}.tt-msg-box .tt-content{vertical-align:middle;color:var(--info-msg-font-color);background-color:var(--default-bg-panel-active-color);background:var(--info-msg-bg-gradient);border-radius:0 5px 5px 0;padding:10px;font-size:13px;position:relative}.tt-message-box{color:var(--info-msg-font-color);box-shadow:var(--info-msg-box-shadow);border-radius:5px;margin-top:10px;font-size:13px;display:flex}.tt-message-box .tt-message-icon-wrap{background:var(--info-msg-grey-gradient);border-radius:5px 0 0 5px;width:34px}.tt-message-box .tt-message-icon{background:var(--info-msg-horizontal-gradient);border-radius:5px 0 0 5px;justify-content:center;width:34px;height:100%;display:flex}.tt-message-box .tt-svg{width:34px;height:34px}.tt-message-box .tt-message-wrap{background-color:var(--default-bg-panel-active-color);background:var(--info-msg-bg-gradient);border-radius:0 5px 5px 0;flex-grow:1;align-items:center;padding:10px;display:flex}.tt-message-box .tt-message{flex-grow:1}.tt-svg{width:128px;height:128px}.tt-svg .tt-svg-upper{stroke:#000;fill:#000}.tt-svg .tt-svg-lower{stroke:#568725;fill:#568725}#sidebarroot .pill{cursor:pointer;background-color:var(--default-bg-panel-color);min-height:22px;color:var(--default-font-color);border-top-right-radius:5px;border-bottom-right-radius:5px;align-items:center;margin-top:2px;text-decoration:none;display:flex;overflow:hidden}#sidebarroot .pill:not([icon]){box-sizing:border-box;padding-top:5px;padding-bottom:5px}#sidebarroot .pill:not([icon]),#sidebarroot .pill[icon] span{height:100%;color:var(--default-font-color);justify-content:flex-start;align-items:center;padding-left:8px;text-decoration:none;display:flex;overflow:hidden}body.tt-tablet #sidebarroot .pill{min-height:34px}body[data-layout=hospital] #sidebarroot .pill{margin-top:0;margin-bottom:1px}#sidebarroot .pill:hover{background-color:var(--default-bg-panel-active-color)!important}.tt-sidebar-information{flex-direction:column;display:flex}.tt-sidebar-information .title{color:inherit;margin:inherit;font-weight:700;text-decoration:none}.tt-sidebar-information .countdown.short{color:var(--tt-color-red)}.tt-sidebar-information .countdown.medium{color:var(--tt-color-orange)}.tt-top-icons{gap:10px;display:flex}");
	_css(":root{--tt-color-green:#00a500;--tt-color-light-green:#acea00;--tt-color-red:#d83500;--tt-color-green--20:#00a50033;--tt-color-green--30:#00a5004d;--tt-color-green--40:#00a50066;--tt-background-torn-gray:repeating-linear-gradient(90deg, #627e0d, #627e0d 2px, #6e8820 0, #6e8820 4px);--tt-background-green:repeating-linear-gradient(90deg, #627e0d, #627e0d 2px, #6e8820 0, #6e8820 4px);--tt-background-alternative:repeating-linear-gradient(90deg, #242424, #242424 2px, #2e2e2e 0, #2e2e2e 4px)}body:not(.dark-mode){--tt-color-blue:blue;--tt-color-orange:orange;--tt-color-item-text:#678c00;--tt-color-item-quantity:black;--tt-background-popup:#f1f1f1;--tt-shadow-popup:unset}body.dark-mode{--tt-color-blue:#058cff;--tt-color-orange:gold;--tt-color-item-text:#9c0;--tt-color-item-quantity:#ddd;--tt-background-popup:#444;--tt-shadow-popup:0 0 10px black}.tt-color-green{color:var(--tt-color-green)}.tt-color-red{color:var(--tt-color-red)}");
	function handleDeviceSizeClasses() {
		checkDevice().then(({ mobile, tablet }) => {
			if (mobile) document.body.classList.add("tt-mobile");
			else document.body.classList.remove("tt-mobile");
			if (tablet) document.body.classList.add("tt-tablet");
			else document.body.classList.remove("tt-tablet");
		});
	}
	var ScriptFeatureManager = class {
		constructor() {
			this.getScriptState();
		}
		createPopup() {}
		isEnabled(featureConstructor) {
			return this.getScriptState().enabled[new featureConstructor().name];
		}
		registerFeature(feature) {
			if (feature.requiresScreenInformation()) handleDeviceSizeClasses();
			feature.initialise();
			feature.execute();
			this.getScriptState().enabled[feature.name] = true;
			if (feature.shouldTriggerEvents()) triggerCustomListener(EVENT_CHANNELS.FEATURE_ENABLED, { name: feature.name });
		}
		getScriptState() {
			const win = RUNTIME_INFORMATION.getWindow();
			if (!win.ttScriptState) {
				const newState = { enabled: {} };
				win.ttScriptState = newState;
				return newState;
			}
			return win.ttScriptState;
		}
	};
	function isPDA() {
		return typeof PDA_evaluateJavascript === "function";
	}
	function registerCoreUserscriptContext() {
		setRuntimeInformation(UserscriptRuntimeInformation);
		setFeatureManager(new ScriptFeatureManager());
		setEventHandler(ScriptEventHandler);
		initializeScriptTheme();
	}
	function initializeScriptTheme() {
		document.documentElement.style.setProperty("--tt-theme-color", "#fff");
		document.documentElement.style.setProperty("--tt-theme-background", "var(--tt-background-green)");
	}
	var UserscriptRuntimeInformation = {
		getWindow() {
			return unsafeWindow;
		},
		getVersion() {
			return GM.info.version;
		},
		isUserscript() {
			return true;
		},
		reloadWindow(force) {
			if (isPDA()) window.flutter_inappwebview.callHandler("reloadPage");
			else location.reload(force);
		}
	};
	var ScriptEventHandler = {
		triggerEvent(channel, payload) {
			document.dispatchEvent(new CustomEvent(`TT_${channel}`, { detail: payload }));
		},
		registerListener(channel, listener) {
			document.addEventListener(`TT_${channel}`, (event) => {
				if (!isCustomEvent(event)) return;
				listener(event.detail);
			});
		},
		get eventRoot() {
			return document;
		},
		triggerEventCrossWorld(target, channel, payload) {
			target.dispatchEvent(new CustomEvent(`TT_${channel}`, { detail: payload }));
		},
		registerListenerCrossWorld(target, channel, listener) {
			target.addEventListener(`TT_${channel}`, (event) => {
				if (!isCustomEvent(event)) return;
				listener(event.detail);
			});
		}
	};
	var TornToolsStorage = class {
		async update(key, fn) {
			const database = await this.get(key);
			fn(database);
			await this.set({ [key]: database });
		}
		async change(object) {
			const keys = Object.keys(object);
			for (const key of keys) {
				const data = this.recursive(await this.get(key), object[key]);
				await this.set({ [key]: data });
			}
		}
		recursive(parent, toChange) {
			for (const key in toChange) if (parent && typeof parent === "object" && !Array.isArray(parent[key]) && key in parent && typeof toChange[key] === "object" && !Array.isArray(toChange[key]) && toChange[key] !== null) parent[key] = this.recursive(parent[key], toChange[key]);
			else if (parent && typeof parent === "object") {
				const value = toChange[key];
				parent[key] = Array.isArray(value) ? Array.from(value) : value;
			} else parent = { [key]: toChange[key] };
			return parent;
		}
	};
	var TTScriptStorage = class extends TornToolsStorage {
		prefix;
		constructor(prefix) {
			super();
			this.prefix = prefix;
		}
		storageKey(key) {
			return key === "cache" ? key : `${this.prefix}_${key}`;
		}
		async get(key) {
			if (Array.isArray(key)) return await Promise.all(key.map((k) => this.storageKey(k)).map((k) => GM.getValue(k)));
			else if (key) return await GM.getValue(this.storageKey(key));
			else {
				const storageKeys = Object.keys(DEFAULT_STORAGE);
				const storageValues = await this.get(storageKeys);
				return storageKeys.reduce((total, k, i) => {
					total[k] = storageValues[i];
					return total;
				}, {});
			}
		}
		async set(object) {
			await Promise.all(Object.entries(object).map(([key, value]) => {
				UserscriptRuntimeStorage.callback({ [key]: {
					newValue: value,
					oldValue: null
				} }, "local");
				return GM.setValue(this.storageKey(key), value);
			}));
		}
		remove(_key) {
			throw new Error("Method not implemented.");
		}
		clear() {
			throw new Error("Method not implemented.");
		}
		reset(_key) {
			throw new Error("Method not implemented.");
		}
		getSize() {
			throw new Error("Method not implemented.");
		}
	};
	var PDAScriptStorage = class extends TornToolsStorage {
		async get(key) {
			if (Array.isArray(key)) return await Promise.all(key.map(this.get.bind(this)));
			else if (key) {
				if (key === "cache") return await GM.getValue(key);
				else return await PDA_storage.get(key);
			} else {
				const storageKeys = Object.keys(DEFAULT_STORAGE);
				const storageValues = await this.get(storageKeys);
				return storageKeys.reduce((total, k, i) => {
					total[k] = storageValues[i];
					return total;
				}, {});
			}
		}
		async set(object) {
			await Promise.all(Object.entries(object).map(([key, value]) => {
				UserscriptRuntimeStorage.callback({ [key]: {
					newValue: value,
					oldValue: null
				} }, "local");
				if (key === "cache") return GM.setValue(key, value);
				else return PDA_storage.set(key, value);
			}));
		}
		async remove(key) {
			if (typeof key === "string") await PDA_storage.delete(key);
			else await Promise.all(key.map(PDA_storage.delete));
		}
		async clear() {
			await Promise.all((await PDA_storage.list()).map(PDA_storage.delete));
		}
		reset(_key) {
			throw new Error("Method not implemented.");
		}
		async getSize() {
			return (await PDA_storage.usage()).used;
		}
	};
	async function registerDatabaseUserscriptContext(storagePrefix) {
		setTTStorage(isPDA() ? new PDAScriptStorage() : new TTScriptStorage(storagePrefix));
		setRuntimeStorage(UserscriptRuntimeStorage);
		await migrateDatabase(true);
		initializeDatabaseListener();
		const [localdata, filters, cache] = await ttStorage.get([
			"localdata",
			"filters",
			"cache"
		]);
		setLocaldata(localdata ? localdata : getDefaultStorage(DEFAULT_STORAGE.localdata));
		setFilters(filters ? filters : getDefaultStorage(DEFAULT_STORAGE.filters));
		ttCache.cache = cache ? cache : getDefaultStorage(DEFAULT_STORAGE.cache);
	}
	var UserscriptRuntimeStorage = {
		callback: () => {},
		addChangeListener(callback) {
			this.callback = callback;
		}
	};
	var RequestListenerInjector = class {
		injectListeners;
		id;
		constructor(injectListeners) {
			this.injectListeners = injectListeners;
			this.id = capitalizeText(injectListeners.name);
		}
		inject() {
			if (this.isInjected()) return;
			this.injectListeners();
			this.setInjected();
		}
		isInjected() {
			return document.documentElement.dataset[`tt${this.id}`] === "true";
		}
		setInjected() {
			document.documentElement.dataset[`tt${this.id}`] = "true";
		}
	};
	function injectFetchListeners() {
		const oldFetch = RUNTIME_INFORMATION.getWindow().fetch;
		RUNTIME_INFORMATION.getWindow().fetch = (input, init) => new Promise((resolve, reject) => {
			oldFetch(input, init).then(async (response) => {
				const page = response.url.substring(response.url.indexOf("torn.com/") + 9, response.url.indexOf(".php"));
				let json = {};
				try {
					json = await response.clone().json();
				} catch {}
				let body = null;
				if (init) {
					if (typeof init.body === "object" && init.body?.constructor?.name === "FormData") {
						const newBody = {};
						for (const [key, value] of [...init.body]) if (isIntNumber(value)) newBody[key] = parseFloat(value);
						else newBody[key] = value;
						body = newBody;
					}
				}
				const url = response.url || input;
				const detail = {
					page,
					json,
					text: await response.clone().text(),
					fetch: {
						url,
						body,
						status: response.status
					}
				};
				window.dispatchEvent(new CustomEvent("tt-fetch", { detail }));
				resolve(response);
			}).catch((error) => {
				reject(error);
			});
		});
	}
	function injectXhrListeners() {
		const oldXHROpen = window.XMLHttpRequest.prototype.open;
		const oldXHRSend = window.XMLHttpRequest.prototype.send;
		window.XMLHttpRequest.prototype.open = function(method, url) {
			this["method"] = method;
			this["url"] = url;
			this["params"] = this["params"] ?? {};
			this.addEventListener("readystatechange", function() {
				if (this.readyState > 3 && this.status === 200) {
					const page = this.responseURL.substring(this.responseURL.indexOf("torn.com/") + 9, this.responseURL.indexOf(".php"));
					let json, uri;
					if (isJsonString(this.response)) json = JSON.parse(this.response);
					else uri = getUrlParams(this.responseURL);
					let text;
					if (this.responseType === "" || this.responseType === "text") text = this.responseText;
					window.dispatchEvent(new CustomEvent("tt-xhr", { detail: {
						page,
						json,
						uri,
						xhr: {
							requestBody: this["requestBody"],
							response: this.response,
							responseType: this.responseType,
							responseText: text,
							responseURL: this.responseURL
						}
					} }));
				}
			});
			arguments[0] = method;
			arguments[1] = url;
			return oldXHROpen.apply(this, arguments);
		};
		window.XMLHttpRequest.prototype.send = function(body) {
			this["params"] = this["params"] ?? {};
			if ("xhrSendAdjustments" in window && typeof window.xhrSendAdjustments === "object") for (const key in window.xhrSendAdjustments) {
				if (typeof window.xhrSendAdjustments[key] !== "function") continue;
				body = window.xhrSendAdjustments[key]({ ...this }, body);
			}
			this["requestBody"] = body;
			arguments[0] = body;
			return oldXHRSend.apply(this, arguments);
		};
	}
	function getUrlParams(url, prop) {
		if (!url) url = location.href;
		const definitions = decodeURIComponent(url.slice(url.indexOf("?") + 1)).split("&");
		const params = {};
		definitions.forEach((val) => {
			const parts = val.split("=", 2);
			params[parts[0]] = parts[1];
		});
		return prop && prop in params ? params[prop] : params;
	}
	function isJsonString(str) {
		if (!str || str === "") return false;
		try {
			JSON.parse(str);
		} catch {
			return false;
		}
		return true;
	}
	var BASE_HIGHLIGHT_SIZE = 38;
	var SYNC_ATTEMPT_INTERVAL = 250;
	var SYNC_ATTEMPT_LIMIT = 80;
	function injectCityItemsMapListeners(pageWindow = window) {
		if (pageWindow.__ttCityItemsMap?.injected) return;
		const state = {
			injected: true,
			entries: [],
			overlays: new Map()
		};
		pageWindow.__ttCityItemsMap = state;
		EVENT_HANDLER.registerListenerCrossWorld(pageWindow, EVENT_CHANNELS.CITY_ITEMS_MAP__SET_ITEMS, ({ entries }) => {
			state.entries = Array.isArray(entries) ? entries.filter(isCityItemsMapEntry) : [];
			scheduleSync();
		});
		EVENT_HANDLER.registerListenerCrossWorld(pageWindow, EVENT_CHANNELS.CITY_ITEMS_MAP__REQUEST_MODEL_ITEMS, () => {
			const items = getModelItems();
			EVENT_HANDLER.triggerEventCrossWorld(pageWindow, EVENT_CHANNELS.CITY_ITEMS_MAP__MODEL_ITEMS, { items });
		});
		EVENT_HANDLER.registerListenerCrossWorld(pageWindow, EVENT_CHANNELS.CITY_ITEMS_MAP__CLEAR, clearOverlays);
		function scheduleSync() {
			let attempts = 0;
			syncOverlays();
			if (state.syncTimer) return;
			state.syncTimer = pageWindow.setInterval(() => {
				attempts++;
				if (syncOverlays() || attempts >= SYNC_ATTEMPT_LIMIT || !state.entries.length) {
					if (state.syncTimer) pageWindow.clearInterval(state.syncTimer);
					state.syncTimer = void 0;
				}
			}, SYNC_ATTEMPT_INTERVAL);
		}
		function syncOverlays() {
			const map = getMap();
			const leaflet = pageWindow.L;
			if (!map || !isLeafletOverlayRuntime(leaflet)) return false;
			const activeEntryIds = new Set(state.entries.map((entry) => entry.entryId));
			for (const [entryId, record] of state.overlays) if (!activeEntryIds.has(entryId)) {
				removeOverlay(record);
				state.overlays.delete(entryId);
			}
			for (const entry of state.entries) {
				let record = state.overlays.get(entry.entryId);
				const latLng = getLatLngForEntry(entry);
				if (!latLng) continue;
				if (!record) {
					record = {
						entry,
						marker: null,
						latLng
					};
					state.overlays.set(entry.entryId, record);
				} else {
					record.entry = entry;
					record.latLng = latLng;
				}
				ensureOverlay(record, map, leaflet);
			}
			return state.entries.every((entry) => !!state.overlays.get(entry.entryId)?.marker);
		}
		function ensureOverlay(record, map, leaflet) {
			const latLng = record.latLng;
			if (!latLng) return;
			try {
				if (record.marker?._map && record.marker._map !== map) removeOverlay(record);
				if (record.marker) {
					record.marker.setLatLng?.(latLng);
					updateOverlayElement(record);
					return;
				}
				const icon = leaflet.divIcon({
					className: "tt-city-item-overlay city-item",
					html: `<span class="tt-city-item-overlay-content"><img src="${getItemImageUrl(record.entry.itemId)}" alt=""></span>`,
					iconSize: [BASE_HIGHLIGHT_SIZE, BASE_HIGHLIGHT_SIZE],
					iconAnchor: [BASE_HIGHLIGHT_SIZE / 2, BASE_HIGHLIGHT_SIZE / 2]
				});
				const marker = leaflet.marker(latLng, {
					icon,
					interactive: true,
					keyboard: false,
					zIndexOffset: 1e3
				});
				if (typeof marker.addTo !== "function") return;
				marker.addTo(map);
				record.marker = marker;
				updateOverlayElement(record);
			} catch {
				record.marker = null;
			}
		}
		function updateOverlayElement(record) {
			const element = record.marker?.getElement?.();
			if (!element) return;
			element.classList.add("tt-city-item-overlay", "city-item");
			element.dataset.id = record.entry.itemId.toString();
			element.dataset.itemId = record.entry.itemId.toString();
			element.dataset.entryId = record.entry.entryId;
			element.dataset.td = record.entry.td;
			element.removeAttribute("title");
		}
		function clearOverlays() {
			state.entries = [];
			if (state.syncTimer) {
				pageWindow.clearInterval(state.syncTimer);
				state.syncTimer = void 0;
			}
			for (const record of state.overlays.values()) removeOverlay(record);
			state.overlays.clear();
		}
		function removeOverlay(record) {
			if (!record.marker) return;
			try {
				if (record.marker.remove) record.marker.remove();
				else record.marker.removeFrom?.(getMap());
			} catch {
				try {
					record.marker._map?.removeLayer?.(record.marker);
				} catch {}
			}
			record.marker = null;
		}
		function getMap() {
			const mapElement = pageWindow.document.querySelector("#map");
			const map = getTornRuntime()?.map?.lmap ?? mapElement?._leaflet_map;
			return isLeafletMap(map) ? map : null;
		}
		function getLatLngForEntry(entry) {
			if (!Number.isFinite(entry.x) || !Number.isFinite(entry.y)) return null;
			const tornMap = getTornRuntime()?.map;
			const leaflet = pageWindow.L;
			try {
				if (tornMap?.getLPoint && leaflet?.CRS?.EPSG3857?.pointToLatLng) {
					const point = [entry.x / 2, entry.y / 2];
					const leafletPoint = tornMap.getLPoint(point);
					return normalizeLatLng(leaflet.CRS.EPSG3857.pointToLatLng(leafletPoint, tornMap.minZoom));
				}
			} catch {}
			return null;
		}
		function getModelItems() {
			const model = getTornRuntime()?.model;
			if (!model) return [];
			try {
				const fullModel = model.get();
				if (Array.isArray(fullModel?.territoryUserItems)) return fullModel.territoryUserItems;
			} catch {}
			try {
				const userItems = model.get("territoryUserItems");
				if (Array.isArray(userItems)) return userItems;
			} catch {}
			return [];
		}
		function getTornRuntime() {
			const torn = pageWindow.torn;
			return isTornRuntime(torn) ? torn : null;
		}
	}
	function isCityItemsMapEntry(value) {
		return isRecord(value) && typeof value.entryId === "string" && typeof value.itemId === "number" && Number.isFinite(value.itemId) && typeof value.name === "string" && typeof value.td === "string" && typeof value.x === "number" && Number.isFinite(value.x) && typeof value.y === "number" && Number.isFinite(value.y);
	}
	function isRecord(value) {
		return typeof value === "object" && value !== null;
	}
	function isTornRuntime(value) {
		return isRecord(value) && (!("map" in value) || value.map == null || isTornMapRuntime(value.map)) && (!("model" in value) || value.model == null || isTornModelRuntime(value.model));
	}
	function isTornMapRuntime(value) {
		return isRecord(value) && (!("lmap" in value) || value.lmap == null || isLeafletMap(value.lmap)) && (!("minZoom" in value) || value.minZoom == null || typeof value.minZoom === "number") && (!("getLPoint" in value) || value.getLPoint == null || typeof value.getLPoint === "function");
	}
	function isTornModelRuntime(value) {
		return isRecord(value) && typeof value.get === "function";
	}
	function isLeafletMap(value) {
		return isRecord(value) && typeof value.addLayer === "function";
	}
	function isLeafletOverlayRuntime(value) {
		return isRecord(value) && typeof value.divIcon === "function" && typeof value.marker === "function";
	}
	function normalizeLatLng(latLng) {
		if (!latLng) return null;
		if (Array.isArray(latLng)) {
			const [lat, lng] = latLng;
			return Number.isFinite(lat) && Number.isFinite(lng) ? latLng : null;
		}
		return Number.isFinite(latLng.lat) && Number.isFinite(latLng.lng) ? latLng : null;
	}
	function getItemImageUrl(itemId) {
		return `https://www.torn.com/images/items/${itemId}/small.png`;
	}
	function injectEfficientRehabListeners(pageWindow = window) {
		EVENT_HANDLER.registerListenerCrossWorld(pageWindow, EVENT_CHANNELS.EFFICIENT_REHAB, ({ ticks }) => {
			const $slider = $("#rehub-progress .ui-slider");
			const rehabPercentages = JSON.parse($slider.attr("data-percentages")) || [];
			if (!(ticks in rehabPercentages)) {
				console.warn("TornTools - Failed to update the rehab amount due to it being an invalid amount of ticks");
				return;
			}
			const percentage = rehabPercentages[ticks];
			$slider.slider("value", percentage).slider("option", "slide")({}, { value: $slider.slider("value") });
		});
		EVENT_HANDLER.triggerEventCrossWorld(pageWindow, EVENT_CHANNELS.EFFICIENT_REHAB__INJECTED);
	}
	function registerInjectorUserscriptContext() {
		setScriptInjector(UserscriptScriptInjector);
	}
	function injectUserscriptCityItemsMapListeners() {
		injectCityItemsMapListeners(unsafeWindow);
	}
	function injectUserscriptEfficientRehabListeners() {
		injectEfficientRehabListeners(unsafeWindow);
	}
	var fetchListenerInjector = new RequestListenerInjector(injectFetchListeners);
	var xhrListenerInjector = new RequestListenerInjector(injectXhrListeners);
	var cityItemsMapListenerInjector = new RequestListenerInjector(injectUserscriptCityItemsMapListeners);
	var efficientRehabInjector = new RequestListenerInjector(injectUserscriptEfficientRehabListeners);
	var UserscriptScriptInjector = {
		injectFetch() {
			fetchListenerInjector.inject();
		},
		injectXHR() {
			xhrListenerInjector.inject();
		},
		injectCityItemsMap() {
			cityItemsMapListenerInjector.inject();
		},
		injectEfficientRehab() {
			efficientRehabInjector.inject();
		}
	};
	var BADGE_TYPES = {
		default: {
			text: "",
			color: null
		},
		error: {
			text: "error",
			color: "#FF0000"
		},
		count: {
			text: async (options) => {
				if (options.events && options.messages) return `${options.events}/${options.messages}`;
				else if (options.events) return options.events.toString();
				else if (options.messages) return options.messages.toString();
				else return await getBadgeText() === "error" ? "error" : null;
			},
			color: async (options) => {
				if (options.events && options.messages) return "#1ed2ac";
				else if (options.events) return "#009eda";
				else if (options.messages) return "#84af03";
				else return await getBadgeText() === "error" ? "error" : null;
			}
		}
	};
	async function setBadge(type, partialOptions = {}) {
		if (SCRIPT_TYPE !== "BACKGROUND") return false;
		const options = {
			events: 0,
			messages: 0,
			...partialOptions
		};
		const badge = { ...BADGE_TYPES[type] };
		if (typeof badge.text === "function") badge.text = await badge.text(options);
		if (typeof badge.color === "function") badge.color = await badge.color(options);
		if (!badge.text) badge.text = "";
		browser.action.setBadgeText({ text: badge.text || "" });
		if (badge.color) browser.action.setBadgeBackgroundColor({ color: badge.color });
		return true;
	}
	function getBadgeText() {
		if (SCRIPT_TYPE !== "BACKGROUND") return Promise.resolve(null);
		return browser.action.getBadgeText({});
	}
	var CUSTOM_API_ERROR = {
		NO_NETWORK: "tt-no_network",
		NO_PERMISSION: "tt-no_permission",
		CANCELLED: "tt-cancelled"
	};
	var FETCH_PLATFORMS = {
		tornv2: "https://api.torn.com/v2/",
		torn_direct: "https://www.torn.com/",
		yata: "https://yata.yt/",
		tornstats: "https://www.tornstats.com/",
		torntools: "https://torntools.gregork.com/",
		nukefamily: "https://nuke.family/",
		uhc: "https://tornuhc.eu/",
		stig: "https://api.no1irishstig.co.uk/",
		prometheus: "https://prombot.co.uk:8443/",
		lzpt: "https://api.lzpt.io/",
		wtf: "https://what-the-f.de/",
		tornw3b: "https://weav3r.dev/",
		ffscouter: "https://ffscouter.com/",
		laekna: "https://laekna-revive-bot.onrender.com/",
		tornintel: "https://torn-intel.com/",
		playground_torntools: "https://torntools.tornplayground.eu/"
	};
	var TORN_API_PLATFORMS = ["tornv2"];
	var TEXT_RESPONSE_PLATFORMS = ["torn_direct", "laekna"];
	async function fetchData(location, partialOptions = {}) {
		const options = mergeOptions(partialOptions);
		if (options.relay && SCRIPT_TYPE !== "BACKGROUND" && !RUNTIME_INFORMATION.isUserscript()) return relayToBackground(location, options);
		const request = buildFetchRequest(location, options);
		let result;
		try {
			result = parseFetchResponse(await DATA_FETCHER.fetch(request.url, {
				method: request.method,
				...request.method === "POST" ? { body: request.body } : {},
				headers: request.headers,
				timeout: decideTimeoutTimer(location)
			}), location);
		} catch (error) {
			return await handleError(location, options, error);
		}
		if (!result.success) return await handleError(location, options, result);
		else if (isApiErrorResponse(result.data)) return await handleError(location, options, result.data);
		await handleTornApiState(location, options);
		return result.data;
	}
	function mergeOptions(partial) {
		return {
			section: "",
			id: void 0,
			selections: [],
			legacySelections: [],
			key: void 0,
			action: void 0,
			method: "GET",
			body: void 0,
			silent: false,
			includeKey: false,
			relay: false,
			params: {},
			...partial
		};
	}
	async function relayToBackground(location, options) {
		return OFFLOAD_SERVICE.fetchRelay(location, {
			...options,
			relay: false
		});
	}
	function decideTimeoutTimer(location) {
		switch (location) {
			case "yata": return 30 * TO_MILLIS.SECONDS;
			default: return 10 * TO_MILLIS.SECONDS;
		}
	}
	function buildFetchRequest(location, options) {
		const url = buildUrl(location, options);
		const headers = buildHeaders(location, options);
		if (options.method === "POST") return {
			url,
			method: options.method,
			body: buildBody(options),
			headers
		};
		else return {
			url,
			method: options.method,
			headers
		};
	}
	function buildUrl(location, options) {
		let path, pathSections;
		let key;
		const params = new URLSearchParams();
		switch (location) {
			case "tornv2":
				path = `${options.section}/${options.id || ""}`;
				params.append("selections", [...options.selections, ...options.legacySelections].join(","));
				params.append("legacy", options.legacySelections.join(","));
				if (settings.apiUsage.comment) params.append("comment", settings.apiUsage.comment);
				break;
			case "torn_direct":
				path = options.action;
				params.set("rfcv", getRFC());
				break;
			case "tornstats":
				pathSections = [
					"api",
					"v2",
					options.key || api.tornstats.key || api.torn.key || ""
				];
				if (options.section) pathSections.push(options.section);
				if (options.id) pathSections.push(options.id);
				path = pathSections.join("/");
				break;
			case "yata":
				pathSections = [
					"api",
					"v1",
					options.section
				];
				if (options.id) pathSections.push(options.id, "");
				if (options.includeKey) key = api.yata.key;
				path = pathSections.join("/");
				break;
			case "prometheus":
				path = ["api", options.section].join("/");
				break;
			case "tornw3b":
				path = ["api", options.section].join("/");
				break;
			case "ffscouter":
				path = [
					"api",
					"v1",
					options.section
				].join("/");
				key = api.ffScouter.key;
				break;
			case "tornintel":
				path = ["api", options.section].join("/");
				break;
			case "playground_torntools":
				path = ["api", options.section].join("/");
				break;
			default: path = options.section;
		}
		if (options.includeKey) params.append("key", options.key || key || api.torn.key || "");
		if (options.params) for (const [key, value] of Object.entries(options.params)) params.append(key, value.toString());
		return `${FETCH_PLATFORMS[location]}${path}${params.toString() ? `?${params}` : ""}`;
	}
	function buildHeaders(location, options) {
		const headers = {};
		if (location === "tornv2") headers["Authorization"] = `ApiKey ${options.key || api.torn.key}`;
		if (options.method === "POST") {
			if (!(options.body instanceof URLSearchParams)) headers["content-type"] = "application/json";
			if (location === "torn_direct") headers["x-requested-with"] = "XMLHttpRequest";
		}
		return headers;
	}
	function buildBody(options) {
		if (options.method !== "POST") return null;
		return options.body instanceof URLSearchParams ? options.body : JSON.stringify(options.body);
	}
	function parseFetchResponse(response, location) {
		try {
			return {
				data: JSON.parse(response.text),
				success: true
			};
		} catch {
			if (TEXT_RESPONSE_PLATFORMS.includes(location)) return {
				data: response.text,
				success: true
			};
			if (response.ok) return { success: true };
			return {
				success: false,
				error: new HTTPException(response.status)
			};
		}
	}
	async function handleError(location, options, result) {
		if (result instanceof DOMException) return handleTimeoutError(location, options);
		if (result instanceof TypeError) return handleNetworkError(location, options, result.message);
		return handleApiError(location, options, result);
	}
	async function handleTimeoutError(location, options) {
		const error = "Request cancelled because it took too long.";
		await handleTornApiState(location, options, error);
		throw {
			error,
			isLocal: false,
			code: CUSTOM_API_ERROR.CANCELLED
		};
	}
	async function handleTornApiState(location, options, error, online = false) {
		if (!TORN_API_PLATFORMS.includes(location) || options.silent || SCRIPT_TYPE !== "BACKGROUND") return;
		if (error) {
			await ttStorage.change({ api: { torn: {
				online,
				error
			} } });
			await setBadge("error");
		} else {
			await getBadgeText().then((value) => {
				if (value === "error") return setBadge("default");
			}).catch(() => console.error("TT - Couldn't get the badge text."));
			await ttStorage.change({ api: { torn: {
				online: true,
				error: ""
			} } });
		}
	}
	async function handleNetworkError(location, options, message) {
		let error = message;
		let isLocal = false;
		let code;
		if (error === "Failed to fetch") {
			isLocal = true;
			if (!RUNTIME_INFORMATION.isUserscript() && SCRIPT_TYPE === "BACKGROUND" && !await hasOrigins(FETCH_PLATFORMS[location])) {
				error = "Permission issues";
				code = CUSTOM_API_ERROR.NO_PERMISSION;
			} else {
				error = "Network issues";
				code = CUSTOM_API_ERROR.NO_NETWORK;
			}
		}
		await handleTornApiState(location, options, error);
		throw {
			error,
			isLocal,
			code
		};
	}
	async function hasOrigins(...origins) {
		return browser.permissions.contains({ origins });
	}
	async function handleApiError(location, options, result) {
		if (TORN_API_PLATFORMS.includes(location)) {
			let error, online;
			if (result.error instanceof HTTPException) {
				error = result.error.toString();
				online = false;
			} else {
				error = result.error.error;
				online = result.error.code !== 9 && !(result instanceof HTTPException);
			}
			await handleTornApiState(location, options, error, online);
			throw result.error instanceof HTTPException ? result.error.asObject() : result.error;
		}
		throw { error: result.error };
	}
	function isApiErrorResponse(data) {
		return !!data && typeof data === "object" && "error" in data;
	}
	var HTTPException = class HTTPException {
		code;
		constructor(code) {
			this.code = code;
		}
		get message() {
			return this.code in HTTPException.codes ? HTTPException.codes[this.code] : `Unknown code (${this.code})`;
		}
		asObject() {
			return {
				code: this.code,
				message: this.message,
				http: true
			};
		}
		toString() {
			return `HTTP ${this.code}: ${this.message}`;
		}
		static get codes() {
			return {
				200: "OK",
				201: "Created",
				202: "Accepted",
				203: "Non-Authoritative Information",
				204: "No Content",
				205: "Reset Content",
				206: "Partial Content",
				300: "Multiple Choices",
				301: "Moved Permanently",
				302: "Found",
				303: "See Other",
				304: "Not Modified",
				305: "Use Proxy",
				306: "Unused",
				307: "Temporary Redirect",
				400: "Bad Request",
				401: "Unauthorized",
				402: "Payment Required",
				403: "Forbidden",
				404: "Not Found",
				405: "Method Not Allowed",
				406: "Not Acceptable",
				407: "Proxy Authentication Required",
				408: "Request Timeout",
				409: "Conflict",
				410: "Gone",
				411: "Length Required",
				412: "Precondition Required",
				413: "Request Entry Too Large",
				414: "Request-URI Too Long",
				415: "Unsupported Media Type",
				416: "Requested Range Not Satisfiable",
				417: "Expectation Failed",
				418: "I'm a teapot",
				429: "Too Many Requests",
				500: "Internal Server Error",
				501: "Not Implemented",
				502: "Bad Gateway",
				503: "Service Unavailable",
				504: "Gateway Timeout",
				505: "HTTP Version Not Supported"
			};
		}
	};
	var ScriptItemResolver = {
		items: [],
		itemsMap: {},
		loadItem(id) {
			return this.getFullItem(id) ?? this.getStaticItem(id);
		},
		findItem(matcher) {
			return this.getAllFullItems().find(matcher) ?? null;
		},
		getStaticItem(id) {
			return this.getFullItem(id);
		},
		hasFullItems: () => true,
		getFullItem(id) {
			if (!Object.keys(this.itemsMap).length) throw new Error("no items loaded");
			return id in this.itemsMap ? this.itemsMap[id] : null;
		},
		async loadItems() {
			if (ttCache.hasValue("static-data", "items-map")) {
				const map = ttCache.get("static-data", "items-map");
				this.items = Object.values(map);
				this.itemsMap = map;
				return;
			}
			const itemsMap = (await fetchData("playground_torntools", { section: "static-items" })).items.reduce((acc, item) => {
				acc[item.id] = item;
				return acc;
			}, {});
			this.items = Object.values(itemsMap);
			this.itemsMap = itemsMap;
			ttCache.set({ "static-data": { "items-map": itemsMap } }, millisToNewDay());
		},
		getAllFullItems() {
			return this.items;
		},
		getAllStaticItems() {
			return this.getAllFullItems();
		}
	};
	function registerNetworkUserscriptContext() {
		setOffloadService(ScriptOffloadService);
		setDataFetcher(ScriptDataFetcher);
		setStaticItemResolver(ScriptItemResolver);
	}
	var ScriptOffloadService = {
		fetchRelay(_location, _options) {
			return Promise.reject(new Error("OffloadService is not available in script context. Use DataFetcher instead."));
		},
		initialize() {
			return Promise.resolve({ success: true });
		},
		reinitializeTimers() {
			return Promise.resolve();
		}
	};
	var ScriptDataFetcher = { fetch(url, options) {
		if (url.startsWith(FETCH_PLATFORMS.torn_direct)) return fetchOnPage(url, options);
		return new Promise((resolve, reject) => {
			try {
				const u = new URL(url);
				u.searchParams.append("pda-cache-busting", getUUID());
				url = u.toString();
			} catch {}
			GM.xmlHttpRequest({
				method: options?.method || "GET",
				url,
				headers: options?.headers,
				data: options?.method === "POST" ? typeof options.body === "string" ? options.body : JSON.stringify(options.body) : void 0,
				timeout: options?.timeout,
				onload: (response) => {
					if (!response) {
						reject(new Error("Request has no actual response. Likely something went wrong in the fetch implementation."));
						return;
					}
					resolve({
						text: response.responseText,
						status: response.status,
						ok: response.status >= 200 && response.status < 300
					});
				},
				onerror: (error) => reject(error),
				ontimeout: () => reject(new DOMException("Request cancelled because it took too long.", "AbortError"))
			});
		});
	} };
	async function fetchOnPage(url, options) {
		const controller = new AbortController();
		const timeoutId = options?.timeout ? setTimeout(() => controller.abort(), options.timeout) : void 0;
		try {
			const response = await fetch(url, {
				method: options?.method || "GET",
				...options?.method === "POST" ? { body: options.body } : {},
				headers: options?.headers,
				signal: controller.signal
			});
			return {
				text: await response.text(),
				status: response.status,
				ok: response.ok
			};
		} finally {
			if (timeoutId) clearTimeout(timeoutId);
		}
	}
	(async () => {
		registerCoreUserscriptContext();
		await registerDatabaseUserscriptContext("tt_ospv");
		registerInjectorUserscriptContext();
		registerNetworkUserscriptContext();
		await ScriptItemResolver.loadItems();
		FEATURE_MANAGER.registerFeature(new OpenedSupplyPackValueFeature());
	})();
})();