TORN: TornTools - Weapon Experience

Show your weapon experience while attacking.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name             TORN: TornTools - Weapon Experience
// @namespace        torntools.weapon-experience
// @version          1.0.2
// @author           DeKleineKobini [2114440] and the TornTools team
// @description      Show your weapon experience while attacking.
// @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/page.php?sid=attack*
// @connect          api.torn.com
// @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);
	};
	_css(" ._keyField_1p5ds_1{color:currentColor;background:#00000060;border-radius:4px;padding-block:2px;padding-inline:4px}\n/*$vite$:1*/ ");
	var FEATURE_MANAGER;
	var ttStorage;
	var RUNTIME_INFORMATION;
	var RUNTIME_STORAGE;
	var OFFLOAD_SERVICE;
	var DATA_FETCHER;
	var EVENT_HANDLER;
	function setFeatureManager(featureManager) {
		FEATURE_MANAGER = featureManager;
	}
	function setTTStorage(storage) {
		ttStorage = storage;
	}
	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 setEventHandler(eventHandler) {
		EVENT_HANDLER = eventHandler;
	}
	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;
		}
		async 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;
		}
	};
	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)
				},
				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)
				},
				property: {
					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)
				},
				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: {
					burglaryFilter: new DefaultSetting("boolean", true),
					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)
				},
				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", "")
			},
			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)
			},
			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", [])
			},
			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", [])
			},
			burglary: {
				targetName: new DefaultSetting("string", ""),
				targetType: new DefaultSetting("array", [])
			},
			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: new DefaultSetting("object", {}),
		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", [])
		},
		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;
					break;
			}
			else newStorage[key] = getDefaultStorage(defaultStorage[key]);
		} else newStorage[key] = defaultStorage[key];
		return newStorage;
	}
	_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}");
	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();
		});
	}
	function svgImport(svgImport) {
		if (typeof svgImport !== "string") return (attributes = {}) => createFallbackElement(attributes);
		if (svgImport.startsWith("data:image/svg+xml")) {
			const encodedData = svgImport.substring(19);
			let svgContent;
			try {
				svgContent = decodeURIComponent(encodedData);
			} catch (error) {
				console.error("Failed to decode SVG data URL", error);
				return (attributes = {}) => createFallbackElement(attributes);
			}
			return (attributes = {}) => createSvgElement(svgContent, attributes);
		}
		return (attributes = {}) => createSvgElement(svgImport, attributes);
	}
	function createFallbackElement(attributes) {
		const svgNS = "http://www.w3.org/2000/svg";
		const svg = document.createElementNS(svgNS, "svg");
		svg.setAttribute("width", "24");
		svg.setAttribute("height", "24");
		svg.setAttribute("viewBox", "0 0 24 24");
		Object.entries(attributes).filter(([, value]) => value !== false && value !== null && value !== void 0).map(([key, value]) => svg.setAttribute(key, String(value)));
		const rect = document.createElementNS(svgNS, "rect");
		rect.setAttribute("x", "0");
		rect.setAttribute("y", "0");
		rect.setAttribute("width", "24");
		rect.setAttribute("height", "24");
		rect.setAttribute("fill", "red");
		svg.appendChild(rect);
		return svg;
	}
	function createSvgElement(svgContent, attributes = {}) {
		const fullAttributes = {
			width: "size" in attributes ? attributes.size : "1em",
			height: "size" in attributes ? attributes.size : "1em",
			...attributes
		};
		const svg = elementBuilder({
			type: "template",
			html: svgContent.trim()
		}).content.firstChild;
		if (!isSVGElement(svg)) return createFallbackElement(fullAttributes);
		Object.entries(fullAttributes).filter(([, value]) => value !== false && value !== null && value !== void 0).forEach(([key, value]) => svg.setAttribute(key, String(value)));
		return svg;
	}
	var caret_down_fill_default = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20256%20256'%20fill='currentColor'%3e%3cpath%20d='M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z'/%3e%3c/svg%3e";
	var lazyImport = (svgData) => {
		let factory;
		return (attributes) => {
			factory ??= svgImport(svgData);
			return factory(attributes);
		};
	};
	var PHFillCaretDown = lazyImport(caret_down_fill_default);
	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.");
	}
	function findAllElements(selector, parent = document) {
		return Array.from(parent.querySelectorAll(selector));
	}
	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 isSVGElement(node) {
		return !!node && node instanceof SVGElement;
	}
	function isCustomEvent(event) {
		return event instanceof CustomEvent;
	}
	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";
	})();
	var TO_MILLIS = {
		SECONDS: 1e3,
		MINUTES: 1e3 * 60,
		HOURS: 1e3 * 60 * 60,
		DAYS: 1e3 * 60 * 60 * 24
	};
	function getUUID() {
		return `_${Math.random().toString(36).substr(2, 9)}`;
	}
	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();
	}
	var MIGRATIONS = [
		{
			id: "9da14c73-0145-4b1d-90e3-0363a5b57499",
			version: "9.0.0",
			execute(_database, flags, _oldStorage) {
				flags.updateUserdata = true;
			}
		},
		{
			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(() => {});
			}
		}
	];
	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,
			updateTorndata: false,
			clearCache: false
		};
		migrations.reverse().filter((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.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.log("TT - Database loaded.", database);
			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.log("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 = {};
			if (!(key in oldStorage)) oldStorage[key] = {};
			const defaultValue = defaultStorage[key];
			if (typeof defaultValue === "object" && defaultValue !== null) if (defaultValue instanceof DefaultSetting) newStorage[key] = migrateDefaultSetting(oldStorage[key], defaultValue);
			else newStorage[key] = convertStorage(oldStorage[key], defaultValue);
			else newStorage[key] = oldStorage[key] ?? defaultValue;
		}
		return newStorage;
	}
	function migrateDefaultSetting(oldValue, setting) {
		if (isValidSettingValue(oldValue, setting)) return oldValue;
		if (setting.defaultValue) 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;
						break;
				}
				if (storageListeners[key]) storageListeners[key].forEach((listener) => listener(changes[key].oldValue, changes[key].newValue));
			}
		});
		initializedDatabaseListeners = true;
	}
	function setUserdata(data) {
		userdata = data;
	}
	function setLocaldata(data) {
		localdata = data;
	}
	function setFilters(data) {
		filters = data;
	}
	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({});
	}
	function hasAPIData() {
		const hasKey = !!api?.torn?.key;
		const hasError = !!api?.torn?.error && !api.torn.error.includes("Backend error") && api.torn.error !== "Network issues";
		const hasUserdata = !!(userdata && Object.keys(userdata).length);
		return hasKey && !hasError && hasUserdata;
	}
	function camelCase(text, lowerCamelCase = true) {
		return (text.trim().charAt(0)[lowerCamelCase ? "toLowerCase" : "toUpperCase"]() + text.slice(1)).trim().replaceAll(" ", "");
	}
	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/loader.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;
	}
	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;
				break;
		}
		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.constructor.name === "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"
			};
		}
	};
	_css(".tt-weapon{position:relative}.tt-weapon-experience{color:var(--tt-color-green);font-size:10px;position:absolute;top:16px;left:9px}");
	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 observers = [];
	async function initialiseListeners() {
		if (mobile || tablet) {
			const area = await requireElement("[class*='weaponList___']");
			new MutationObserver(showExperience).observe(area, { childList: true });
		}
	}
	async function showExperience() {
		const attacker = (await requireElement("[class*='green___']")).parentElement;
		for (const weapon of findAllElements("#weapon_main, #weapon_second, #weapon_melee, #weapon_temp", attacker)) {
			if (weapon.className.includes("defender")) continue;
			const name = weapon.querySelector("figure > img[alt]")?.getAttribute("alt");
			if (!name) continue;
			const experience = userdata.weaponexp.find((item) => item.name === name)?.exp;
			if (!experience) continue;
			const observer = new MutationObserver(() => {
				const target = attacker.querySelector(`#${weapon.id}`);
				if (!target) return;
				if (!target.classList.contains("tt-weapon")) weapon.classList.add("tt-weapon");
				if (!target.querySelector(".tt-weapon-experience")) weapon.appendChild(elementBuilder({
					type: "div",
					class: "tt-weapon-experience",
					text: `XP: ${experience}%`
				}));
			});
			observer.observe(weapon, {
				childList: true,
				attributes: true
			});
			observers.push(observer);
			weapon.classList.add("tt-weapon");
			weapon.appendChild(elementBuilder({
				type: "div",
				class: "tt-weapon-experience",
				text: `XP: ${experience}%`
			}));
		}
	}
	function removeExperience() {
		while (observers.length) observers.pop().disconnect();
		findAllElements(".tt-weapon").forEach((weapon) => weapon.classList.remove("tt-weapon"));
		findAllElements(".tt-weapon-experience").forEach((experience) => experience.remove());
	}
	var WeaponExperienceFeature = class extends Feature {
		constructor() {
			super("Weapon Experience", "attack");
		}
		isEnabled() {
			return settings.pages.attack.weaponExperience;
		}
		async initialise() {
			await initialiseListeners();
		}
		async execute() {
			await showExperience();
		}
		cleanup() {
			removeExperience();
		}
		storageKeys() {
			return ["settings.pages.attack.weaponExperience"];
		}
		requirements() {
			if (!hasAPIData() || !settings.apiUsage.user.weaponexp) 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)}");
	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";
		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);
	}
	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 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;
		}
	};
	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;
		}
	};
	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.");
		}
	};
	async function registerDatabaseUserscriptContext(storagePrefix) {
		setTTStorage(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;
		}
	};
	function registerNetworkUserscriptContext() {
		setOffloadService(ScriptOffloadService);
		setDataFetcher(ScriptDataFetcher);
	}
	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) {
		console.debug("TT Userscripts - DataFetcher - Preparing fetch");
		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 {}
			console.debug("TT Userscripts - DataFetcher - Fetching through background", typeof GM, typeof GM !== "undefined" ? typeof GM.xmlHttpRequest : "N/A");
			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) => {
					console.debug("TT Userscripts - DataFetcher - 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) => {
					console.debug("TT Userscripts - DataFetcher - onError", error);
					reject(error);
				},
				ontimeout: () => {
					console.debug("TT Userscripts - DataFetcher - ontimeout");
					reject(new DOMException("Request cancelled because it took too long.", "AbortError"));
				}
			});
			console.debug("TT Userscripts - DataFetcher - Launched fetch");
		});
	} };
	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 function checkAPIPermission(key) {
		try {
			const { type, faction, company } = (await fetchData("tornv2", {
				section: "key",
				selections: ["info"],
				key,
				silent: true
			})).info.access;
			if (type === "Limited Access" || type === "Full Access") return {
				access: true,
				faction,
				company
			};
			else return { access: false };
		} catch (error) {
			throw error.error;
		}
	}
	_css(".tt-container.spacer{margin-bottom:10px}.tt-container .title{text-shadow:1px 1px 2px #000000a6;letter-spacing:1px;white-space:nowrap;height:30px;margin:initial;align-items:center;padding-left:10px;font-size:13px;display:flex}.tt-theme,.tt-container.tt-theme-background .title{color:var(--tt-theme-color);background:var(--tt-theme-background)}.tt-container.collapsible .title{cursor:pointer}.tt-container.spacer .title{margin-top:10px}.tt-container .title .text{width:-webkit-fill-available;width:-moz-available}.tt-container .title .icon{text-align:center;min-width:30px;margin:auto;font-size:16px;position:static!important}.tt-container.rounding.always-content .title,.tt-container.rounding .title:not(.collapsed){border-radius:5px 5px 0 0}.tt-container.rounding:not(.always-content) .title.collapsed{border-radius:5px}.tt-container .title.collapsed .icon{transform:rotate(-90deg)}.tt-container:not(.always-content) .title.collapsed+main,.tt-container.always-content .title.collapsed+main .hide-collapse{display:none!important}.tt-container .title .options{flex-direction:row-reverse;align-items:center;width:100%;margin-right:4px;display:flex}.tt-container .title .options>*{align-items:center;margin-right:4px;display:flex}.tt-container .title .options i{font-size:1rem}#sidebarroot .tt-container .title{border-top-right-radius:5px;border-bottom-right-radius:5px;align-items:center;height:22px}body.tt-tablet #sidebarroot .tt-container .title{height:34px}.tt-container>main{margin-top:initial!important}.tt-container:not(.compact)>main{padding:4px 4px 3px}.tt-container.rounding>main{border-radius:0 0 5px 5px}.tt-container>main.background{background-color:var(--default-bg-panel-color)}.tt-container.reset-styles{letter-spacing:0;color:var(--default-color)!important;font-family:Arial,serif!important}.tt-container input[type=checkbox]{accent-color:#6e8820}");
	function createContainer(title, partialOptions) {
		const options = {
			id: camelCase(title),
			class: [],
			showHeader: true,
			onlyHeader: false,
			collapsible: true,
			applyRounding: true,
			spacer: false,
			contentBackground: true,
			allowDragging: false,
			flexContainer: false,
			compact: false,
			alwaysContent: false,
			filter: false,
			resetStyles: false,
			...partialOptions
		};
		if (options.onlyHeader) options.collapsible = false;
		const { container, collapsed } = _createContainer(title, options);
		let parentElement;
		if ("parentElement" in options) parentElement = options.parentElement;
		else if ("nextElement" in options) parentElement = options.nextElement.parentElement;
		else if ("previousElement" in options) parentElement = options.previousElement.parentElement;
		else parentElement = document.querySelector(".content-wrapper");
		if ("nextElement" in options) parentElement.insertBefore(container, options.nextElement);
		else if ("previousElement" in options) parentElement.insertBefore(container, options.previousElement.nextSibling);
		else parentElement.appendChild(container);
		return {
			container,
			content: container.querySelector(":scope > main"),
			options: container.querySelector(".options"),
			collapsed
		};
		function _createContainer(title, options) {
			document.querySelector(`#${options.id}`)?.remove();
			const containerClasses = ["tt-container"];
			if (options.collapsible) containerClasses.push("collapsible");
			if (options.applyRounding) containerClasses.push("rounding");
			if (options.spacer) containerClasses.push("spacer");
			if (options.compact) containerClasses.push("compact");
			if (options.alwaysContent) containerClasses.push("always-content");
			if (options.class) {
				let classes;
				if (typeof options.class === "string") classes = options.class.split(" ").filter((c) => !!c);
				else classes = options.class.filter((c) => !!c);
				containerClasses.push(...classes);
			}
			if (options.filter) containerClasses.push("tt-filter");
			if (options.resetStyles) containerClasses.push("reset-styles");
			const mainClasses = [];
			if (options.contentBackground) mainClasses.push("background");
			if (options.flexContainer) mainClasses.push("t-flex");
			containerClasses.push("tt-theme-background");
			const container = elementBuilder({
				type: "div",
				class: containerClasses.join(" "),
				id: options.id
			});
			const collapsed = options.onlyHeader || options.collapsible && (options.id in filters.containers ? filters.containers[options.id] : false);
			if (options.showHeader) container.appendChild(elementBuilder({
				type: "div",
				class: ["title", collapsed ? "collapsed" : null],
				children: [
					elementBuilder({
						type: "div",
						class: "text",
						text: title
					}),
					elementBuilder({
						type: "div",
						class: "options"
					}),
					options.collapsible ? PHFillCaretDown({ class: "icon" }) : null
				]
			}));
			if (!options.onlyHeader) container.appendChild(elementBuilder({
				type: "main",
				class: mainClasses
			}));
			if (options.collapsible) container.querySelector(".title").addEventListener("click", async () => {
				container.querySelector(".title").classList.toggle("collapsed");
				await ttStorage.change({ filters: { containers: { [options.id]: container.querySelector(".title").classList.contains("collapsed") } } });
			});
			if (options.allowDragging) {
				const content = container.querySelector(":scope > main");
				content.addEventListener("dragover", (event) => event.preventDefault());
				content.addEventListener("drop", (event) => {
					if (content.querySelector(".temp.item, .temp.quick-item")) content.querySelector(".temp.item, .temp.quick-item").classList.remove("temp");
					event.preventDefault();
					event.dataTransfer?.clearData();
				});
			}
			return {
				container,
				collapsed
			};
		}
	}
	function findContainer(title, partialOptions = {}) {
		const options = {
			id: camelCase(title),
			selector: void 0,
			...partialOptions
		};
		if (!options.id) return null;
		const container = document.querySelector(`#${options.id}`);
		if (!container) return null;
		if (options.selector) return container.querySelector(options.selector);
		else return container;
	}
	function removeContainer(title, partialOptions = {}) {
		const container = findContainer(title, partialOptions);
		if (!container) return;
		container.remove();
	}
	var script_fetch_module_default = { keyField: "_keyField_1p5ds_1" };
	var PDA_KEY = "###PDA-APIKEY###";
	var STORAGE_LOCATION = "tt_scripts_key";
	async function requiresAPIKey() {
		if (isPDA()) return PDA_KEY;
		const storageKey = localStorage.getItem(STORAGE_LOCATION);
		if (storageKey) return storageKey;
		return new Promise((resolve) => {
			const { options } = createContainer("TornTools Userscript - API Key", {
				nextElement: document.querySelector(".main-items-cont-wrap"),
				onlyHeader: true,
				class: "mb10"
			});
			const keyInput = elementBuilder({
				type: "input",
				class: script_fetch_module_default.keyField
			});
			options.appendChild(elementBuilder({
				type: "button",
				class: ["preference-button", script_fetch_module_default.keyField],
				text: "Save",
				events: { async click() {
					const key = keyInput.value;
					if ((await checkAPIPermission(key)).access) {
						localStorage.setItem(STORAGE_LOCATION, key);
						removeContainer("TornTools Userscript - API Key");
						resolve(key);
					} else window.alert("Not a valid key or not enough permissions (limited access).");
				} }
			}));
			options.appendChild(keyInput);
		});
	}
	function isPDA() {
		return PDA_KEY.at(0) !== "#";
	}
	(async () => {
		registerCoreUserscriptContext();
		await registerDatabaseUserscriptContext("tt_we");
		registerNetworkUserscriptContext();
		await fetchWeaponExperienceData(await requiresAPIKey());
		const feature = new WeaponExperienceFeature();
		FEATURE_MANAGER.registerFeature(feature);
	})();
	async function fetchWeaponExperienceData(key) {
		const cachedWeaponExperience = ttCache.get("tt-weapon-experience");
		if (cachedWeaponExperience) {
			setUserdata({
				...userdata,
				weaponexp: cachedWeaponExperience
			});
			return;
		}
		const data = await fetchData("tornv2", {
			section: "user",
			selections: ["weaponexp"],
			key,
			includeKey: true
		});
		ttCache.set({ "tt-weapon-experience": data.weaponexp }, TO_MILLIS.MINUTES * 15);
		setUserdata({
			...userdata,
			...data
		});
	}
})();