Kxs Client - Survev.io Client

A client to enhance the survev.io in-game experience with many features, as well as future features.

  1. // ==UserScript==
  2. // @name Kxs Client - Survev.io Client
  3. // @namespace https://github.com/Kisakay/KxsClient
  4. // @version 2.1.4
  5. // @description A client to enhance the survev.io in-game experience with many features, as well as future features.
  6. // @author Kisakay
  7. // @license AGPL-3.0
  8. // @run-at document-end
  9. // @icon https://kxs.rip/assets/KysClientLogo.png
  10. // @match *://survev.io/*
  11. // @match *://66.179.254.36/*
  12. // @match *://zurviv.io/*
  13. // @match *://expandedwater.online/*
  14. // @match *://localhost:3000/*
  15. // @match *://surviv.wf/*
  16. // @match *://resurviv.biz/*
  17. // @match *://82.67.125.203/*
  18. // @match *://leia-uwu.github.io/survev/*
  19. // @match *://50v50.online/*
  20. // @match *://eu-comp.net/*
  21. // @match *://survev.leia-is.gay/*
  22. // @match *://survivx.org
  23. // @grant none
  24. // ==/UserScript==
  25. ;
  26. /******/ (() => { // webpackBootstrap
  27. /******/ var __webpack_modules__ = ({
  28.  
  29. /***/ 123:
  30. /***/ ((module) => {
  31.  
  32. const numeric = /^[0-9]+$/
  33. const compareIdentifiers = (a, b) => {
  34. const anum = numeric.test(a)
  35. const bnum = numeric.test(b)
  36.  
  37. if (anum && bnum) {
  38. a = +a
  39. b = +b
  40. }
  41.  
  42. return a === b ? 0
  43. : (anum && !bnum) ? -1
  44. : (bnum && !anum) ? 1
  45. : a < b ? -1
  46. : 1
  47. }
  48.  
  49. const rcompareIdentifiers = (a, b) => compareIdentifiers(b, a)
  50.  
  51. module.exports = {
  52. compareIdentifiers,
  53. rcompareIdentifiers,
  54. }
  55.  
  56.  
  57. /***/ }),
  58.  
  59. /***/ 272:
  60. /***/ ((module) => {
  61.  
  62. const debug = (
  63. typeof process === 'object' &&
  64. process.env &&
  65. process.env.NODE_DEBUG &&
  66. /\bsemver\b/i.test(process.env.NODE_DEBUG)
  67. ) ? (...args) => console.error('SEMVER', ...args)
  68. : () => {}
  69.  
  70. module.exports = debug
  71.  
  72.  
  73. /***/ }),
  74.  
  75. /***/ 560:
  76. /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
  77.  
  78. const SemVer = __webpack_require__(908)
  79. const compare = (a, b, loose) =>
  80. new SemVer(a, loose).compare(new SemVer(b, loose))
  81.  
  82. module.exports = compare
  83.  
  84.  
  85. /***/ }),
  86.  
  87. /***/ 580:
  88. /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
  89.  
  90. const compare = __webpack_require__(560)
  91. const gt = (a, b, loose) => compare(a, b, loose) > 0
  92. module.exports = gt
  93.  
  94.  
  95. /***/ }),
  96.  
  97. /***/ 587:
  98. /***/ ((module) => {
  99.  
  100. // parse out just the options we care about
  101. const looseOption = Object.freeze({ loose: true })
  102. const emptyOpts = Object.freeze({ })
  103. const parseOptions = options => {
  104. if (!options) {
  105. return emptyOpts
  106. }
  107.  
  108. if (typeof options !== 'object') {
  109. return looseOption
  110. }
  111.  
  112. return options
  113. }
  114. module.exports = parseOptions
  115.  
  116.  
  117. /***/ }),
  118.  
  119. /***/ 718:
  120. /***/ ((module, exports, __webpack_require__) => {
  121.  
  122. const {
  123. MAX_SAFE_COMPONENT_LENGTH,
  124. MAX_SAFE_BUILD_LENGTH,
  125. MAX_LENGTH,
  126. } = __webpack_require__(874)
  127. const debug = __webpack_require__(272)
  128. exports = module.exports = {}
  129.  
  130. // The actual regexps go on exports.re
  131. const re = exports.re = []
  132. const safeRe = exports.safeRe = []
  133. const src = exports.src = []
  134. const safeSrc = exports.safeSrc = []
  135. const t = exports.t = {}
  136. let R = 0
  137.  
  138. const LETTERDASHNUMBER = '[a-zA-Z0-9-]'
  139.  
  140. // Replace some greedy regex tokens to prevent regex dos issues. These regex are
  141. // used internally via the safeRe object since all inputs in this library get
  142. // normalized first to trim and collapse all extra whitespace. The original
  143. // regexes are exported for userland consumption and lower level usage. A
  144. // future breaking change could export the safer regex only with a note that
  145. // all input should have extra whitespace removed.
  146. const safeRegexReplacements = [
  147. ['\\s', 1],
  148. ['\\d', MAX_LENGTH],
  149. [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH],
  150. ]
  151.  
  152. const makeSafeRegex = (value) => {
  153. for (const [token, max] of safeRegexReplacements) {
  154. value = value
  155. .split(`${token}*`).join(`${token}{0,${max}}`)
  156. .split(`${token}+`).join(`${token}{1,${max}}`)
  157. }
  158. return value
  159. }
  160.  
  161. const createToken = (name, value, isGlobal) => {
  162. const safe = makeSafeRegex(value)
  163. const index = R++
  164. debug(name, index, value)
  165. t[name] = index
  166. src[index] = value
  167. safeSrc[index] = safe
  168. re[index] = new RegExp(value, isGlobal ? 'g' : undefined)
  169. safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined)
  170. }
  171.  
  172. // The following Regular Expressions can be used for tokenizing,
  173. // validating, and parsing SemVer version strings.
  174.  
  175. // ## Numeric Identifier
  176. // A single `0`, or a non-zero digit followed by zero or more digits.
  177.  
  178. createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*')
  179. createToken('NUMERICIDENTIFIERLOOSE', '\\d+')
  180.  
  181. // ## Non-numeric Identifier
  182. // Zero or more digits, followed by a letter or hyphen, and then zero or
  183. // more letters, digits, or hyphens.
  184.  
  185. createToken('NONNUMERICIDENTIFIER', `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`)
  186.  
  187. // ## Main Version
  188. // Three dot-separated numeric identifiers.
  189.  
  190. createToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\.` +
  191. `(${src[t.NUMERICIDENTIFIER]})\\.` +
  192. `(${src[t.NUMERICIDENTIFIER]})`)
  193.  
  194. createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` +
  195. `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` +
  196. `(${src[t.NUMERICIDENTIFIERLOOSE]})`)
  197.  
  198. // ## Pre-release Version Identifier
  199. // A numeric identifier, or a non-numeric identifier.
  200.  
  201. createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NUMERICIDENTIFIER]
  202. }|${src[t.NONNUMERICIDENTIFIER]})`)
  203.  
  204. createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NUMERICIDENTIFIERLOOSE]
  205. }|${src[t.NONNUMERICIDENTIFIER]})`)
  206.  
  207. // ## Pre-release Version
  208. // Hyphen, followed by one or more dot-separated pre-release version
  209. // identifiers.
  210.  
  211. createToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER]
  212. }(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`)
  213.  
  214. createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE]
  215. }(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`)
  216.  
  217. // ## Build Metadata Identifier
  218. // Any combination of digits, letters, or hyphens.
  219.  
  220. createToken('BUILDIDENTIFIER', `${LETTERDASHNUMBER}+`)
  221.  
  222. // ## Build Metadata
  223. // Plus sign, followed by one or more period-separated build metadata
  224. // identifiers.
  225.  
  226. createToken('BUILD', `(?:\\+(${src[t.BUILDIDENTIFIER]
  227. }(?:\\.${src[t.BUILDIDENTIFIER]})*))`)
  228.  
  229. // ## Full Version String
  230. // A main version, followed optionally by a pre-release version and
  231. // build metadata.
  232.  
  233. // Note that the only major, minor, patch, and pre-release sections of
  234. // the version string are capturing groups. The build metadata is not a
  235. // capturing group, because it should not ever be used in version
  236. // comparison.
  237.  
  238. createToken('FULLPLAIN', `v?${src[t.MAINVERSION]
  239. }${src[t.PRERELEASE]}?${
  240. src[t.BUILD]}?`)
  241.  
  242. createToken('FULL', `^${src[t.FULLPLAIN]}$`)
  243.  
  244. // like full, but allows v1.2.3 and =1.2.3, which people do sometimes.
  245. // also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty
  246. // common in the npm registry.
  247. createToken('LOOSEPLAIN', `[v=\\s]*${src[t.MAINVERSIONLOOSE]
  248. }${src[t.PRERELEASELOOSE]}?${
  249. src[t.BUILD]}?`)
  250.  
  251. createToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`)
  252.  
  253. createToken('GTLT', '((?:<|>)?=?)')
  254.  
  255. // Something like "2.*" or "1.2.x".
  256. // Note that "x.x" is a valid xRange identifer, meaning "any version"
  257. // Only the first item is strictly required.
  258. createToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`)
  259. createToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`)
  260.  
  261. createToken('XRANGEPLAIN', `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})` +
  262. `(?:\\.(${src[t.XRANGEIDENTIFIER]})` +
  263. `(?:\\.(${src[t.XRANGEIDENTIFIER]})` +
  264. `(?:${src[t.PRERELEASE]})?${
  265. src[t.BUILD]}?` +
  266. `)?)?`)
  267.  
  268. createToken('XRANGEPLAINLOOSE', `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` +
  269. `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` +
  270. `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` +
  271. `(?:${src[t.PRERELEASELOOSE]})?${
  272. src[t.BUILD]}?` +
  273. `)?)?`)
  274.  
  275. createToken('XRANGE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`)
  276. createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`)
  277.  
  278. // Coercion.
  279. // Extract anything that could conceivably be a part of a valid semver
  280. createToken('COERCEPLAIN', `${'(^|[^\\d])' +
  281. '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` +
  282. `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` +
  283. `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?`)
  284. createToken('COERCE', `${src[t.COERCEPLAIN]}(?:$|[^\\d])`)
  285. createToken('COERCEFULL', src[t.COERCEPLAIN] +
  286. `(?:${src[t.PRERELEASE]})?` +
  287. `(?:${src[t.BUILD]})?` +
  288. `(?:$|[^\\d])`)
  289. createToken('COERCERTL', src[t.COERCE], true)
  290. createToken('COERCERTLFULL', src[t.COERCEFULL], true)
  291.  
  292. // Tilde ranges.
  293. // Meaning is "reasonably at or greater than"
  294. createToken('LONETILDE', '(?:~>?)')
  295.  
  296. createToken('TILDETRIM', `(\\s*)${src[t.LONETILDE]}\\s+`, true)
  297. exports.tildeTrimReplace = '$1~'
  298.  
  299. createToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`)
  300. createToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`)
  301.  
  302. // Caret ranges.
  303. // Meaning is "at least and backwards compatible with"
  304. createToken('LONECARET', '(?:\\^)')
  305.  
  306. createToken('CARETTRIM', `(\\s*)${src[t.LONECARET]}\\s+`, true)
  307. exports.caretTrimReplace = '$1^'
  308.  
  309. createToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`)
  310. createToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`)
  311.  
  312. // A simple gt/lt/eq thing, or just "" to indicate "any version"
  313. createToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`)
  314. createToken('COMPARATOR', `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`)
  315.  
  316. // An expression to strip any whitespace between the gtlt and the thing
  317. // it modifies, so that `> 1.2.3` ==> `>1.2.3`
  318. createToken('COMPARATORTRIM', `(\\s*)${src[t.GTLT]
  319. }\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true)
  320. exports.comparatorTrimReplace = '$1$2$3'
  321.  
  322. // Something like `1.2.3 - 1.2.4`
  323. // Note that these all use the loose form, because they'll be
  324. // checked against either the strict or loose comparator form
  325. // later.
  326. createToken('HYPHENRANGE', `^\\s*(${src[t.XRANGEPLAIN]})` +
  327. `\\s+-\\s+` +
  328. `(${src[t.XRANGEPLAIN]})` +
  329. `\\s*$`)
  330.  
  331. createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` +
  332. `\\s+-\\s+` +
  333. `(${src[t.XRANGEPLAINLOOSE]})` +
  334. `\\s*$`)
  335.  
  336. // Star ranges basically just allow anything at all.
  337. createToken('STAR', '(<|>)?=?\\s*\\*')
  338. // >=0.0.0 is like a star
  339. createToken('GTE0', '^\\s*>=\\s*0\\.0\\.0\\s*$')
  340. createToken('GTE0PRE', '^\\s*>=\\s*0\\.0\\.0-0\\s*$')
  341.  
  342.  
  343. /***/ }),
  344.  
  345. /***/ 746:
  346. /***/ (() => {
  347.  
  348. "use strict";
  349.  
  350. // --- HOOK GLOBAL WEBSOCKET POUR INTERCEPTION gameId & PTC monitoring ---
  351. (function () {
  352. const OriginalWebSocket = window.WebSocket;
  353. function HookedWebSocket(url, protocols) {
  354. const ws = protocols !== undefined
  355. ? new OriginalWebSocket(url, protocols)
  356. : new OriginalWebSocket(url);
  357. if (typeof url === "string" && url.includes("gameId=")) {
  358. const gameId = url.split("gameId=")[1];
  359. globalThis.kxsClient.kxsNetwork.sendGameInfoToWebSocket(gameId);
  360. }
  361. return ws;
  362. }
  363. // Copie le prototype
  364. HookedWebSocket.prototype = OriginalWebSocket.prototype;
  365. // Copie les propriétés statiques (CONNECTING, OPEN, etc.)
  366. Object.defineProperties(HookedWebSocket, {
  367. CONNECTING: { value: OriginalWebSocket.CONNECTING, writable: false },
  368. OPEN: { value: OriginalWebSocket.OPEN, writable: false },
  369. CLOSING: { value: OriginalWebSocket.CLOSING, writable: false },
  370. CLOSED: { value: OriginalWebSocket.CLOSED, writable: false },
  371. });
  372. // Remplace le constructeur global
  373. window.WebSocket = HookedWebSocket;
  374. })();
  375.  
  376.  
  377. /***/ }),
  378.  
  379. /***/ 814:
  380. /***/ ((__unused_webpack_module, exports) => {
  381.  
  382. "use strict";
  383. var __webpack_unused_export__;
  384.  
  385. __webpack_unused_export__ = ({ value: true });
  386. exports.w = void 0;
  387. ;
  388. class SteganoDB {
  389. data;
  390. currentTable;
  391. options;
  392. database;
  393. constructor(options) {
  394. this.currentTable = options?.tableName || "json";
  395. this.database = options.database || "stegano.db";
  396. this.data = {
  397. [this.currentTable]: []
  398. };
  399. this.fetchDataFromFile();
  400. }
  401. read() { return localStorage.getItem(this.database) || this.data; }
  402. write() { return localStorage.setItem(this.database, JSON.stringify(this.data)); }
  403. setNestedProperty = (object, key, value) => {
  404. const properties = key.split('.');
  405. let currentObject = object;
  406. for (let i = 0; i < properties.length - 1; i++) {
  407. const property = properties[i];
  408. if (typeof currentObject[property] !== 'object' || currentObject[property] === null) {
  409. currentObject[property] = {};
  410. }
  411. currentObject = currentObject[property];
  412. }
  413. currentObject[properties[properties.length - 1]] = value;
  414. };
  415. getNestedProperty = (object, key) => {
  416. const properties = key.split('.');
  417. let index = 0;
  418. for (; index < properties.length; ++index) {
  419. object = object && object[properties[index]];
  420. }
  421. return object;
  422. };
  423. fetchDataFromFile() {
  424. try {
  425. const content = this.read();
  426. this.data = JSON.parse(content);
  427. }
  428. catch (error) {
  429. this.data = { [this.currentTable]: [] };
  430. }
  431. }
  432. updateNestedProperty(key, operation, value) {
  433. const [id, ...rest] = key.split('.');
  434. const nestedPath = rest.join('.');
  435. let currentValue = this.data[this.currentTable].find((entry) => entry.id === id);
  436. if (!currentValue && operation !== 'get') {
  437. currentValue = { id, value: {} };
  438. this.data[this.currentTable].push(currentValue);
  439. }
  440. if (!currentValue && operation === 'get') {
  441. return undefined;
  442. }
  443. switch (operation) {
  444. case 'get':
  445. return nestedPath ? this.getNestedProperty(currentValue.value, nestedPath) : currentValue.value;
  446. case 'set':
  447. if (nestedPath) {
  448. this.setNestedProperty(currentValue.value, nestedPath, value);
  449. }
  450. else {
  451. currentValue.value = value;
  452. }
  453. this.write();
  454. break;
  455. case 'add':
  456. if (!nestedPath) {
  457. currentValue.value = (typeof currentValue.value === 'number' ? currentValue.value : 0) + value;
  458. }
  459. else {
  460. const existingValue = this.getNestedProperty(currentValue.value, nestedPath);
  461. if (typeof existingValue !== 'number' && existingValue !== undefined) {
  462. throw new TypeError('The existing value is not a number.');
  463. }
  464. this.setNestedProperty(currentValue.value, nestedPath, (typeof existingValue === 'number' ? existingValue : 0) + value);
  465. }
  466. this.write();
  467. break;
  468. case 'sub':
  469. if (!nestedPath) {
  470. currentValue.value = (typeof currentValue.value === 'number' ? currentValue.value : 0) - value;
  471. }
  472. else {
  473. const existingValue = this.getNestedProperty(currentValue.value, nestedPath);
  474. if (typeof existingValue !== 'number' && existingValue !== undefined && existingValue !== null) {
  475. throw new TypeError('The existing value is not a number.');
  476. }
  477. this.setNestedProperty(currentValue.value, nestedPath, (typeof existingValue === 'number' ? existingValue : 0) - value);
  478. }
  479. this.write();
  480. break;
  481. case 'delete':
  482. if (nestedPath) {
  483. const properties = nestedPath.split('.');
  484. let currentObject = currentValue.value;
  485. for (let i = 0; i < properties.length - 1; i++) {
  486. const property = properties[i];
  487. if (!currentObject[property]) {
  488. return;
  489. }
  490. currentObject = currentObject[property];
  491. }
  492. delete currentObject[properties[properties.length - 1]];
  493. }
  494. else {
  495. const index = this.data[this.currentTable].findIndex((entry) => entry.id === id);
  496. if (index !== -1) {
  497. this.data[this.currentTable].splice(index, 1);
  498. }
  499. }
  500. this.write();
  501. break;
  502. case 'pull':
  503. const existingArray = nestedPath ? this.getNestedProperty(currentValue.value, nestedPath) : currentValue.value;
  504. if (!Array.isArray(existingArray)) {
  505. throw new Error('The stored value is not an array');
  506. }
  507. const newArray = existingArray.filter((item) => item !== value);
  508. if (nestedPath) {
  509. this.setNestedProperty(currentValue.value, nestedPath, newArray);
  510. }
  511. else {
  512. currentValue.value = newArray;
  513. }
  514. this.write();
  515. break;
  516. }
  517. }
  518. table(tableName) {
  519. if (tableName.includes(" ") || !tableName || tableName === "") {
  520. throw new SyntaxError("Key can't be null or contain a space.");
  521. }
  522. if (!this.data[tableName]) {
  523. this.data[tableName] = [];
  524. }
  525. return new SteganoDB(this.options);
  526. }
  527. get(key) {
  528. return this.updateNestedProperty(key, 'get');
  529. }
  530. set(key, value) {
  531. if (key.includes(" ") || !key || key === "") {
  532. throw new SyntaxError("Key can't be null or contain a space.");
  533. }
  534. this.updateNestedProperty(key, 'set', value);
  535. }
  536. pull(key, value) {
  537. if (key.includes(" ") || !key || key === "") {
  538. throw new SyntaxError("Key can't be null or contain a space.");
  539. }
  540. this.updateNestedProperty(key, 'pull', value);
  541. }
  542. add(key, count) {
  543. if (key.includes(" ") || !key || key === "") {
  544. throw new SyntaxError("Key can't be null or contain a space.");
  545. }
  546. if (isNaN(count)) {
  547. throw new SyntaxError("The value is NaN.");
  548. }
  549. this.updateNestedProperty(key, 'add', count);
  550. }
  551. sub(key, count) {
  552. if (key.includes(" ") || !key || key === "") {
  553. throw new SyntaxError("Key can't be null or contain a space.");
  554. }
  555. if (isNaN(count)) {
  556. throw new SyntaxError("The value is NaN.");
  557. }
  558. this.updateNestedProperty(key, 'sub', count);
  559. }
  560. delete(key) {
  561. this.updateNestedProperty(key, 'delete');
  562. }
  563. cache(key, value, time) {
  564. if (key.includes(" ") || !key || key === "") {
  565. throw new SyntaxError("Key can't be null ou contain a space.");
  566. }
  567. if (!time || isNaN(time)) {
  568. throw new SyntaxError("The time needs to be a number. (ms)");
  569. }
  570. this.updateNestedProperty(key, 'set', value);
  571. setTimeout(() => {
  572. this.updateNestedProperty(key, 'delete');
  573. }, time);
  574. }
  575. push(key, element) {
  576. if (key.includes(" ") || !key || key === "") {
  577. throw new SyntaxError("Key can't be null or contain a space.");
  578. }
  579. const [id, ...rest] = key.split('.');
  580. const nestedPath = rest.join('.');
  581. let currentValue = this.data[this.currentTable].find((entry) => entry.id === id);
  582. if (!currentValue) {
  583. currentValue = { id, value: nestedPath ? {} : [] };
  584. this.data[this.currentTable].push(currentValue);
  585. }
  586. if (nestedPath) {
  587. const existingArray = this.getNestedProperty(currentValue.value, nestedPath);
  588. if (!existingArray) {
  589. this.setNestedProperty(currentValue.value, nestedPath, [element]);
  590. }
  591. else if (!Array.isArray(existingArray)) {
  592. throw new Error('The stored value is not an array');
  593. }
  594. else {
  595. existingArray.push(element);
  596. this.setNestedProperty(currentValue.value, nestedPath, existingArray);
  597. }
  598. }
  599. else {
  600. if (!Array.isArray(currentValue.value)) {
  601. currentValue.value = [];
  602. }
  603. currentValue.value.push(element);
  604. }
  605. this.write();
  606. }
  607. has(key) {
  608. return Boolean(this.get(key));
  609. }
  610. deleteAll() {
  611. this.data[this.currentTable] = [];
  612. this.write();
  613. }
  614. all() {
  615. return this.data[this.currentTable];
  616. }
  617. }
  618. exports.w = SteganoDB;
  619.  
  620.  
  621. /***/ }),
  622.  
  623. /***/ 874:
  624. /***/ ((module) => {
  625.  
  626. // Note: this is the semver.org version of the spec that it implements
  627. // Not necessarily the package version of this code.
  628. const SEMVER_SPEC_VERSION = '2.0.0'
  629.  
  630. const MAX_LENGTH = 256
  631. const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER ||
  632. /* istanbul ignore next */ 9007199254740991
  633.  
  634. // Max safe segment length for coercion.
  635. const MAX_SAFE_COMPONENT_LENGTH = 16
  636.  
  637. // Max safe length for a build identifier. The max length minus 6 characters for
  638. // the shortest version with a build 0.0.0+BUILD.
  639. const MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6
  640.  
  641. const RELEASE_TYPES = [
  642. 'major',
  643. 'premajor',
  644. 'minor',
  645. 'preminor',
  646. 'patch',
  647. 'prepatch',
  648. 'prerelease',
  649. ]
  650.  
  651. module.exports = {
  652. MAX_LENGTH,
  653. MAX_SAFE_COMPONENT_LENGTH,
  654. MAX_SAFE_BUILD_LENGTH,
  655. MAX_SAFE_INTEGER,
  656. RELEASE_TYPES,
  657. SEMVER_SPEC_VERSION,
  658. FLAG_INCLUDE_PRERELEASE: 0b001,
  659. FLAG_LOOSE: 0b010,
  660. }
  661.  
  662.  
  663. /***/ }),
  664.  
  665. /***/ 908:
  666. /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
  667.  
  668. const debug = __webpack_require__(272)
  669. const { MAX_LENGTH, MAX_SAFE_INTEGER } = __webpack_require__(874)
  670. const { safeRe: re, safeSrc: src, t } = __webpack_require__(718)
  671.  
  672. const parseOptions = __webpack_require__(587)
  673. const { compareIdentifiers } = __webpack_require__(123)
  674. class SemVer {
  675. constructor (version, options) {
  676. options = parseOptions(options)
  677.  
  678. if (version instanceof SemVer) {
  679. if (version.loose === !!options.loose &&
  680. version.includePrerelease === !!options.includePrerelease) {
  681. return version
  682. } else {
  683. version = version.version
  684. }
  685. } else if (typeof version !== 'string') {
  686. throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`)
  687. }
  688.  
  689. if (version.length > MAX_LENGTH) {
  690. throw new TypeError(
  691. `version is longer than ${MAX_LENGTH} characters`
  692. )
  693. }
  694.  
  695. debug('SemVer', version, options)
  696. this.options = options
  697. this.loose = !!options.loose
  698. // this isn't actually relevant for versions, but keep it so that we
  699. // don't run into trouble passing this.options around.
  700. this.includePrerelease = !!options.includePrerelease
  701.  
  702. const m = version.trim().match(options.loose ? re[t.LOOSE] : re[t.FULL])
  703.  
  704. if (!m) {
  705. throw new TypeError(`Invalid Version: ${version}`)
  706. }
  707.  
  708. this.raw = version
  709.  
  710. // these are actually numbers
  711. this.major = +m[1]
  712. this.minor = +m[2]
  713. this.patch = +m[3]
  714.  
  715. if (this.major > MAX_SAFE_INTEGER || this.major < 0) {
  716. throw new TypeError('Invalid major version')
  717. }
  718.  
  719. if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) {
  720. throw new TypeError('Invalid minor version')
  721. }
  722.  
  723. if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) {
  724. throw new TypeError('Invalid patch version')
  725. }
  726.  
  727. // numberify any prerelease numeric ids
  728. if (!m[4]) {
  729. this.prerelease = []
  730. } else {
  731. this.prerelease = m[4].split('.').map((id) => {
  732. if (/^[0-9]+$/.test(id)) {
  733. const num = +id
  734. if (num >= 0 && num < MAX_SAFE_INTEGER) {
  735. return num
  736. }
  737. }
  738. return id
  739. })
  740. }
  741.  
  742. this.build = m[5] ? m[5].split('.') : []
  743. this.format()
  744. }
  745.  
  746. format () {
  747. this.version = `${this.major}.${this.minor}.${this.patch}`
  748. if (this.prerelease.length) {
  749. this.version += `-${this.prerelease.join('.')}`
  750. }
  751. return this.version
  752. }
  753.  
  754. toString () {
  755. return this.version
  756. }
  757.  
  758. compare (other) {
  759. debug('SemVer.compare', this.version, this.options, other)
  760. if (!(other instanceof SemVer)) {
  761. if (typeof other === 'string' && other === this.version) {
  762. return 0
  763. }
  764. other = new SemVer(other, this.options)
  765. }
  766.  
  767. if (other.version === this.version) {
  768. return 0
  769. }
  770.  
  771. return this.compareMain(other) || this.comparePre(other)
  772. }
  773.  
  774. compareMain (other) {
  775. if (!(other instanceof SemVer)) {
  776. other = new SemVer(other, this.options)
  777. }
  778.  
  779. return (
  780. compareIdentifiers(this.major, other.major) ||
  781. compareIdentifiers(this.minor, other.minor) ||
  782. compareIdentifiers(this.patch, other.patch)
  783. )
  784. }
  785.  
  786. comparePre (other) {
  787. if (!(other instanceof SemVer)) {
  788. other = new SemVer(other, this.options)
  789. }
  790.  
  791. // NOT having a prerelease is > having one
  792. if (this.prerelease.length && !other.prerelease.length) {
  793. return -1
  794. } else if (!this.prerelease.length && other.prerelease.length) {
  795. return 1
  796. } else if (!this.prerelease.length && !other.prerelease.length) {
  797. return 0
  798. }
  799.  
  800. let i = 0
  801. do {
  802. const a = this.prerelease[i]
  803. const b = other.prerelease[i]
  804. debug('prerelease compare', i, a, b)
  805. if (a === undefined && b === undefined) {
  806. return 0
  807. } else if (b === undefined) {
  808. return 1
  809. } else if (a === undefined) {
  810. return -1
  811. } else if (a === b) {
  812. continue
  813. } else {
  814. return compareIdentifiers(a, b)
  815. }
  816. } while (++i)
  817. }
  818.  
  819. compareBuild (other) {
  820. if (!(other instanceof SemVer)) {
  821. other = new SemVer(other, this.options)
  822. }
  823.  
  824. let i = 0
  825. do {
  826. const a = this.build[i]
  827. const b = other.build[i]
  828. debug('build compare', i, a, b)
  829. if (a === undefined && b === undefined) {
  830. return 0
  831. } else if (b === undefined) {
  832. return 1
  833. } else if (a === undefined) {
  834. return -1
  835. } else if (a === b) {
  836. continue
  837. } else {
  838. return compareIdentifiers(a, b)
  839. }
  840. } while (++i)
  841. }
  842.  
  843. // preminor will bump the version up to the next minor release, and immediately
  844. // down to pre-release. premajor and prepatch work the same way.
  845. inc (release, identifier, identifierBase) {
  846. if (release.startsWith('pre')) {
  847. if (!identifier && identifierBase === false) {
  848. throw new Error('invalid increment argument: identifier is empty')
  849. }
  850. // Avoid an invalid semver results
  851. if (identifier) {
  852. const r = new RegExp(`^${this.options.loose ? src[t.PRERELEASELOOSE] : src[t.PRERELEASE]}$`)
  853. const match = `-${identifier}`.match(r)
  854. if (!match || match[1] !== identifier) {
  855. throw new Error(`invalid identifier: ${identifier}`)
  856. }
  857. }
  858. }
  859.  
  860. switch (release) {
  861. case 'premajor':
  862. this.prerelease.length = 0
  863. this.patch = 0
  864. this.minor = 0
  865. this.major++
  866. this.inc('pre', identifier, identifierBase)
  867. break
  868. case 'preminor':
  869. this.prerelease.length = 0
  870. this.patch = 0
  871. this.minor++
  872. this.inc('pre', identifier, identifierBase)
  873. break
  874. case 'prepatch':
  875. // If this is already a prerelease, it will bump to the next version
  876. // drop any prereleases that might already exist, since they are not
  877. // relevant at this point.
  878. this.prerelease.length = 0
  879. this.inc('patch', identifier, identifierBase)
  880. this.inc('pre', identifier, identifierBase)
  881. break
  882. // If the input is a non-prerelease version, this acts the same as
  883. // prepatch.
  884. case 'prerelease':
  885. if (this.prerelease.length === 0) {
  886. this.inc('patch', identifier, identifierBase)
  887. }
  888. this.inc('pre', identifier, identifierBase)
  889. break
  890. case 'release':
  891. if (this.prerelease.length === 0) {
  892. throw new Error(`version ${this.raw} is not a prerelease`)
  893. }
  894. this.prerelease.length = 0
  895. break
  896.  
  897. case 'major':
  898. // If this is a pre-major version, bump up to the same major version.
  899. // Otherwise increment major.
  900. // 1.0.0-5 bumps to 1.0.0
  901. // 1.1.0 bumps to 2.0.0
  902. if (
  903. this.minor !== 0 ||
  904. this.patch !== 0 ||
  905. this.prerelease.length === 0
  906. ) {
  907. this.major++
  908. }
  909. this.minor = 0
  910. this.patch = 0
  911. this.prerelease = []
  912. break
  913. case 'minor':
  914. // If this is a pre-minor version, bump up to the same minor version.
  915. // Otherwise increment minor.
  916. // 1.2.0-5 bumps to 1.2.0
  917. // 1.2.1 bumps to 1.3.0
  918. if (this.patch !== 0 || this.prerelease.length === 0) {
  919. this.minor++
  920. }
  921. this.patch = 0
  922. this.prerelease = []
  923. break
  924. case 'patch':
  925. // If this is not a pre-release version, it will increment the patch.
  926. // If it is a pre-release it will bump up to the same patch version.
  927. // 1.2.0-5 patches to 1.2.0
  928. // 1.2.0 patches to 1.2.1
  929. if (this.prerelease.length === 0) {
  930. this.patch++
  931. }
  932. this.prerelease = []
  933. break
  934. // This probably shouldn't be used publicly.
  935. // 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction.
  936. case 'pre': {
  937. const base = Number(identifierBase) ? 1 : 0
  938.  
  939. if (this.prerelease.length === 0) {
  940. this.prerelease = [base]
  941. } else {
  942. let i = this.prerelease.length
  943. while (--i >= 0) {
  944. if (typeof this.prerelease[i] === 'number') {
  945. this.prerelease[i]++
  946. i = -2
  947. }
  948. }
  949. if (i === -1) {
  950. // didn't increment anything
  951. if (identifier === this.prerelease.join('.') && identifierBase === false) {
  952. throw new Error('invalid increment argument: identifier already exists')
  953. }
  954. this.prerelease.push(base)
  955. }
  956. }
  957. if (identifier) {
  958. // 1.2.0-beta.1 bumps to 1.2.0-beta.2,
  959. // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0
  960. let prerelease = [identifier, base]
  961. if (identifierBase === false) {
  962. prerelease = [identifier]
  963. }
  964. if (compareIdentifiers(this.prerelease[0], identifier) === 0) {
  965. if (isNaN(this.prerelease[1])) {
  966. this.prerelease = prerelease
  967. }
  968. } else {
  969. this.prerelease = prerelease
  970. }
  971. }
  972. break
  973. }
  974. default:
  975. throw new Error(`invalid increment argument: ${release}`)
  976. }
  977. this.raw = this.format()
  978. if (this.build.length) {
  979. this.raw += `+${this.build.join('.')}`
  980. }
  981. return this
  982. }
  983. }
  984.  
  985. module.exports = SemVer
  986.  
  987.  
  988. /***/ })
  989.  
  990. /******/ });
  991. /************************************************************************/
  992. /******/ // The module cache
  993. /******/ var __webpack_module_cache__ = {};
  994. /******/
  995. /******/ // The require function
  996. /******/ function __webpack_require__(moduleId) {
  997. /******/ // Check if module is in cache
  998. /******/ var cachedModule = __webpack_module_cache__[moduleId];
  999. /******/ if (cachedModule !== undefined) {
  1000. /******/ return cachedModule.exports;
  1001. /******/ }
  1002. /******/ // Create a new module (and put it into the cache)
  1003. /******/ var module = __webpack_module_cache__[moduleId] = {
  1004. /******/ // no module.id needed
  1005. /******/ // no module.loaded needed
  1006. /******/ exports: {}
  1007. /******/ };
  1008. /******/
  1009. /******/ // Execute the module function
  1010. /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  1011. /******/
  1012. /******/ // Return the exports of the module
  1013. /******/ return module.exports;
  1014. /******/ }
  1015. /******/
  1016. /************************************************************************/
  1017. /******/ /* webpack/runtime/compat get default export */
  1018. /******/ (() => {
  1019. /******/ // getDefaultExport function for compatibility with non-harmony modules
  1020. /******/ __webpack_require__.n = (module) => {
  1021. /******/ var getter = module && module.__esModule ?
  1022. /******/ () => (module['default']) :
  1023. /******/ () => (module);
  1024. /******/ __webpack_require__.d(getter, { a: getter });
  1025. /******/ return getter;
  1026. /******/ };
  1027. /******/ })();
  1028. /******/
  1029. /******/ /* webpack/runtime/define property getters */
  1030. /******/ (() => {
  1031. /******/ // define getter functions for harmony exports
  1032. /******/ __webpack_require__.d = (exports, definition) => {
  1033. /******/ for(var key in definition) {
  1034. /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
  1035. /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
  1036. /******/ }
  1037. /******/ }
  1038. /******/ };
  1039. /******/ })();
  1040. /******/
  1041. /******/ /* webpack/runtime/hasOwnProperty shorthand */
  1042. /******/ (() => {
  1043. /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
  1044. /******/ })();
  1045. /******/
  1046. /************************************************************************/
  1047. var __webpack_exports__ = {};
  1048. // This entry needs to be wrapped in an IIFE because it needs to be in strict mode.
  1049. (() => {
  1050. "use strict";
  1051.  
  1052. // EXTERNAL MODULE: ./src/UTILS/websocket-hook.ts
  1053. var websocket_hook = __webpack_require__(746);
  1054. ;// ./config.json
  1055. const config_namespaceObject = /*#__PURE__*/JSON.parse('{"base_url":"https://kxs.rip","api_url":"https://network.kxs.rip","fileName":"KxsClient.user.js","match":["*://survev.io/*","*://66.179.254.36/*","*://zurviv.io/*","*://expandedwater.online/*","*://localhost:3000/*","*://surviv.wf/*","*://resurviv.biz/*","*://82.67.125.203/*","*://leia-uwu.github.io/survev/*","*://50v50.online/*","*://eu-comp.net/*","*://survev.leia-is.gay/*","*://survivx.org"],"grant":["none"]}');
  1056. ;// ./src/UTILS/vars.ts
  1057.  
  1058. const background_song = config_namespaceObject.base_url + "/assets/Stranger_Things_Theme_Song_C418_REMIX.mp3";
  1059. const kxs_logo = config_namespaceObject.base_url + "/assets/KysClientLogo.png";
  1060. const full_logo = config_namespaceObject.base_url + "/assets/KysClient.gif";
  1061. const background_image = config_namespaceObject.base_url + "/assets/background.jpg";
  1062. const win_sound = config_namespaceObject.base_url + "/assets/win.m4a";
  1063. const death_sound = config_namespaceObject.base_url + "/assets/dead.m4a";
  1064.  
  1065. ;// ./src/MECHANIC/intercept.ts
  1066. function intercept(link, targetUrl) {
  1067. const open = XMLHttpRequest.prototype.open;
  1068. XMLHttpRequest.prototype.open = function (method, url) {
  1069. if (url.includes(link)) {
  1070. arguments[1] = targetUrl;
  1071. }
  1072. open.apply(this, arguments);
  1073. };
  1074. const originalFetch = window.fetch;
  1075. window.fetch = function (url, options) {
  1076. if (url.includes(link)) {
  1077. url = targetUrl;
  1078. }
  1079. return originalFetch.apply(this, arguments);
  1080. };
  1081. }
  1082.  
  1083.  
  1084. ;// ./src/HUD/MOD/HealthWarning.ts
  1085. class HealthWarning {
  1086. constructor(kxsClient) {
  1087. this.isDraggable = false;
  1088. this.isDragging = false;
  1089. this.dragOffset = { x: 0, y: 0 };
  1090. this.POSITION_KEY = 'lowHpWarning';
  1091. this.menuCheckInterval = null;
  1092. this.warningElement = null;
  1093. this.kxsClient = kxsClient;
  1094. this.createWarningElement();
  1095. this.setFixedPosition();
  1096. this.setupDragAndDrop();
  1097. this.startMenuCheckInterval();
  1098. }
  1099. createWarningElement() {
  1100. const warning = document.createElement("div");
  1101. const uiTopLeft = document.getElementById("ui-top-left");
  1102. warning.style.cssText = `
  1103. position: fixed;
  1104. background: rgba(0, 0, 0, 0.8);
  1105. border: 2px solid #ff0000;
  1106. border-radius: 5px;
  1107. padding: 10px 15px;
  1108. color: #ff0000;
  1109. font-family: Arial, sans-serif;
  1110. font-size: 14px;
  1111. z-index: 9999;
  1112. display: none;
  1113. backdrop-filter: blur(5px);
  1114. pointer-events: none;
  1115. transition: border-color 0.3s ease;
  1116. `;
  1117. const content = document.createElement("div");
  1118. content.style.cssText = `
  1119. display: flex;
  1120. align-items: center;
  1121. gap: 8px;
  1122. `;
  1123. const icon = document.createElement("div");
  1124. icon.innerHTML = `
  1125. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  1126. <circle cx="12" cy="12" r="10"></circle>
  1127. <line x1="12" y1="8" x2="12" y2="12"></line>
  1128. <line x1="12" y1="16" x2="12.01" y2="16"></line>
  1129. </svg>
  1130. `;
  1131. const text = document.createElement("span");
  1132. text.textContent = "LOW HP!";
  1133. if (uiTopLeft) {
  1134. content.appendChild(icon);
  1135. content.appendChild(text);
  1136. warning.appendChild(content);
  1137. uiTopLeft.appendChild(warning);
  1138. }
  1139. this.warningElement = warning;
  1140. this.addPulseAnimation();
  1141. }
  1142. setFixedPosition() {
  1143. if (!this.warningElement)
  1144. return;
  1145. // Récupérer la position depuis le localStorage ou les valeurs par défaut
  1146. const storageKey = `position_${this.POSITION_KEY}`;
  1147. const savedPosition = localStorage.getItem(storageKey);
  1148. let position;
  1149. if (savedPosition) {
  1150. try {
  1151. // Utiliser la position sauvegardée
  1152. const { x, y } = JSON.parse(savedPosition);
  1153. position = { left: x, top: y };
  1154. }
  1155. catch (error) {
  1156. // En cas d'erreur, utiliser la position par défaut
  1157. position = this.kxsClient.defaultPositions[this.POSITION_KEY];
  1158. this.kxsClient.logger.error('Erreur lors du chargement de la position LOW HP:', error);
  1159. }
  1160. }
  1161. else {
  1162. // Utiliser la position par défaut
  1163. position = this.kxsClient.defaultPositions[this.POSITION_KEY];
  1164. }
  1165. // Appliquer la position
  1166. if (position) {
  1167. this.warningElement.style.top = `${position.top}px`;
  1168. this.warningElement.style.left = `${position.left}px`;
  1169. }
  1170. }
  1171. addPulseAnimation() {
  1172. const keyframes = `
  1173. @keyframes pulse {
  1174. 0% { opacity: 1; }
  1175. 50% { opacity: 0.5; }
  1176. 100% { opacity: 1; }
  1177. }
  1178. `;
  1179. const style = document.createElement("style");
  1180. style.textContent = keyframes;
  1181. document.head.appendChild(style);
  1182. if (this.warningElement) {
  1183. this.warningElement.style.animation = "pulse 1.5s infinite";
  1184. }
  1185. }
  1186. show(health) {
  1187. if (!this.warningElement)
  1188. return;
  1189. this.warningElement.style.display = "block";
  1190. const span = this.warningElement.querySelector("span");
  1191. if (span) {
  1192. span.textContent = `LOW HP: ${health}%`;
  1193. }
  1194. }
  1195. hide() {
  1196. if (!this.warningElement)
  1197. return;
  1198. // Ne pas masquer si en mode placement
  1199. // if (this.isDraggable) return;
  1200. this.warningElement.style.display = "none";
  1201. }
  1202. update(health) {
  1203. // Si le mode placement est actif (isDraggable), on ne fait rien pour maintenir l'affichage
  1204. if (this.isDraggable) {
  1205. return;
  1206. }
  1207. // Sinon, comportement normal
  1208. if (health <= 30 && health > 0) {
  1209. this.show(health);
  1210. }
  1211. else {
  1212. this.hide();
  1213. }
  1214. }
  1215. setupDragAndDrop() {
  1216. // Nous n'avons plus besoin d'écouteurs pour RSHIFT car nous utilisons maintenant
  1217. // l'état du menu secondaire pour déterminer quand activer/désactiver le mode placement
  1218. // Écouteurs d'événements de souris pour le glisser-déposer
  1219. document.addEventListener('mousedown', this.handleMouseDown.bind(this));
  1220. document.addEventListener('mousemove', this.handleMouseMove.bind(this));
  1221. document.addEventListener('mouseup', this.handleMouseUp.bind(this));
  1222. }
  1223. enableDragging() {
  1224. if (!this.warningElement)
  1225. return;
  1226. this.isDraggable = true;
  1227. this.warningElement.style.pointerEvents = 'auto';
  1228. this.warningElement.style.cursor = 'move';
  1229. this.warningElement.style.borderColor = '#00ff00'; // Feedback visuel quand déplaçable
  1230. // Force l'affichage de l'avertissement LOW HP, peu importe la santé actuelle
  1231. this.warningElement.style.display = 'block';
  1232. const span = this.warningElement.querySelector("span");
  1233. if (span) {
  1234. span.textContent = 'LOW HP: Placement Mode';
  1235. }
  1236. }
  1237. disableDragging() {
  1238. if (!this.warningElement)
  1239. return;
  1240. this.isDraggable = false;
  1241. this.isDragging = false;
  1242. this.warningElement.style.pointerEvents = 'none';
  1243. this.warningElement.style.cursor = 'default';
  1244. this.warningElement.style.borderColor = '#ff0000'; // Retour à la couleur normale
  1245. // Remet le texte original si l'avertissement est visible
  1246. if (this.warningElement.style.display === 'block') {
  1247. const span = this.warningElement.querySelector("span");
  1248. if (span) {
  1249. span.textContent = 'LOW HP';
  1250. }
  1251. }
  1252. // Récupérer la santé actuelle à partir de l'élément UI de santé du jeu
  1253. const healthBars = document.querySelectorAll("#ui-health-container");
  1254. if (healthBars.length > 0) {
  1255. const bar = healthBars[0].querySelector("#ui-health-actual");
  1256. if (bar) {
  1257. const currentHealth = Math.round(parseFloat(bar.style.width));
  1258. // Forcer une mise à jour immédiate en fonction de la santé actuelle
  1259. this.update(currentHealth);
  1260. }
  1261. }
  1262. }
  1263. handleMouseDown(event) {
  1264. if (!this.isDraggable || !this.warningElement)
  1265. return;
  1266. // Check if click was on the warning element
  1267. if (this.warningElement.contains(event.target)) {
  1268. this.isDragging = true;
  1269. // Calculate offset from mouse position to element corner
  1270. const rect = this.warningElement.getBoundingClientRect();
  1271. this.dragOffset = {
  1272. x: event.clientX - rect.left,
  1273. y: event.clientY - rect.top
  1274. };
  1275. // Prevent text selection during drag
  1276. event.preventDefault();
  1277. }
  1278. }
  1279. handleMouseMove(event) {
  1280. if (!this.isDragging || !this.warningElement)
  1281. return;
  1282. // Calculate new position
  1283. const newX = event.clientX - this.dragOffset.x;
  1284. const newY = event.clientY - this.dragOffset.y;
  1285. // Update element position
  1286. this.warningElement.style.left = `${newX}px`;
  1287. this.warningElement.style.top = `${newY}px`;
  1288. }
  1289. handleMouseUp() {
  1290. if (this.isDragging && this.warningElement) {
  1291. this.isDragging = false;
  1292. // Récupérer les positions actuelles
  1293. const left = parseInt(this.warningElement.style.left);
  1294. const top = parseInt(this.warningElement.style.top);
  1295. // Sauvegarder la position
  1296. const storageKey = `position_${this.POSITION_KEY}`;
  1297. localStorage.setItem(storageKey, JSON.stringify({ x: left, y: top }));
  1298. }
  1299. }
  1300. startMenuCheckInterval() {
  1301. // Créer un intervalle qui vérifie régulièrement l'état du menu RSHIFT
  1302. this.menuCheckInterval = window.setInterval(() => {
  1303. var _a;
  1304. // Vérifier si le menu secondaire est ouvert
  1305. const isMenuOpen = ((_a = this.kxsClient.secondaryMenu) === null || _a === void 0 ? void 0 : _a.isOpen) || false;
  1306. // Si le menu est ouvert et que nous ne sommes pas en mode placement, activer le mode placement
  1307. if (isMenuOpen && this.kxsClient.isHealthWarningEnabled && !this.isDraggable) {
  1308. this.enableDragging();
  1309. }
  1310. // Si le menu est fermé et que nous sommes en mode placement, désactiver le mode placement
  1311. else if (!isMenuOpen && this.isDraggable) {
  1312. this.disableDragging();
  1313. }
  1314. }, 100); // Vérifier toutes les 100ms
  1315. }
  1316. }
  1317.  
  1318.  
  1319. ;// ./src/MECHANIC/KillLeaderTracking.ts
  1320. class KillLeaderTracker {
  1321. constructor(kxsClient) {
  1322. this.offsetX = 20;
  1323. this.offsetY = 20;
  1324. this.lastKnownKills = 0;
  1325. this.wasKillLeader = false;
  1326. this.MINIMUM_KILLS_FOR_LEADER = 3;
  1327. this.kxsClient = kxsClient;
  1328. this.warningElement = null;
  1329. this.encouragementElement = null;
  1330. this.killLeaderKillCount = 0;
  1331. this.wasKillLeader = false;
  1332. this.createEncouragementElement();
  1333. this.initMouseTracking();
  1334. }
  1335. createEncouragementElement() {
  1336. const encouragement = document.createElement("div");
  1337. encouragement.style.cssText = `
  1338. position: fixed;
  1339. background: rgba(0, 255, 0, 0.1);
  1340. border: 2px solid #00ff00;
  1341. border-radius: 5px;
  1342. padding: 10px 15px;
  1343. color: #00ff00;
  1344. font-family: Arial, sans-serif;
  1345. font-size: 14px;
  1346. z-index: 9999;
  1347. display: none;
  1348. backdrop-filter: blur(5px);
  1349. transition: all 0.3s ease;
  1350. pointer-events: none;
  1351. box-shadow: 0 0 10px rgba(0, 255, 0, 0.3);
  1352. `;
  1353. const content = document.createElement("div");
  1354. content.style.cssText = `
  1355. display: flex;
  1356. align-items: center;
  1357. gap: 8px;
  1358. `;
  1359. const icon = document.createElement("div");
  1360. icon.innerHTML = `
  1361. <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  1362. <path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z"/>
  1363. </svg>
  1364. `;
  1365. const text = document.createElement("span");
  1366. text.textContent = "Nice Kill!";
  1367. content.appendChild(icon);
  1368. content.appendChild(text);
  1369. encouragement.appendChild(content);
  1370. document.body.appendChild(encouragement);
  1371. this.encouragementElement = encouragement;
  1372. this.addEncouragementAnimation();
  1373. }
  1374. initMouseTracking() {
  1375. document.addEventListener("mousemove", (e) => {
  1376. this.updateElementPosition(this.warningElement, e);
  1377. this.updateElementPosition(this.encouragementElement, e);
  1378. });
  1379. }
  1380. updateElementPosition(element, e) {
  1381. if (!element || element.style.display === "none")
  1382. return;
  1383. const x = e.clientX + this.offsetX;
  1384. const y = e.clientY + this.offsetY;
  1385. const rect = element.getBoundingClientRect();
  1386. const maxX = window.innerWidth - rect.width;
  1387. const maxY = window.innerHeight - rect.height;
  1388. const finalX = Math.min(Math.max(0, x), maxX);
  1389. const finalY = Math.min(Math.max(0, y), maxY);
  1390. element.style.transform = `translate(${finalX}px, ${finalY}px)`;
  1391. }
  1392. addEncouragementAnimation() {
  1393. const keyframes = `
  1394. @keyframes encouragementPulse {
  1395. 0% { transform: scale(1); opacity: 1; }
  1396. 50% { transform: scale(1.1); opacity: 0.8; }
  1397. 100% { transform: scale(1); opacity: 1; }
  1398. }
  1399. @keyframes fadeInOut {
  1400. 0% { opacity: 0; transform: translateY(20px); }
  1401. 10% { opacity: 1; transform: translateY(0); }
  1402. 90% { opacity: 1; transform: translateY(0); }
  1403. 100% { opacity: 0; transform: translateY(-20px); }
  1404. }
  1405. `;
  1406. const style = document.createElement("style");
  1407. style.textContent = keyframes;
  1408. document.head.appendChild(style);
  1409. if (this.encouragementElement) {
  1410. this.encouragementElement.style.animation = "fadeInOut 3s forwards";
  1411. }
  1412. }
  1413. showEncouragement(killsToLeader, isDethrone = false, noKillLeader = false) {
  1414. if (!this.encouragementElement)
  1415. return;
  1416. let message;
  1417. if (isDethrone && killsToLeader !== 0) {
  1418. message = "Oh no! You've been dethroned!";
  1419. this.encouragementElement.style.borderColor = "#ff0000";
  1420. this.encouragementElement.style.color = "#ff0000";
  1421. this.encouragementElement.style.background = "rgba(255, 0, 0, 0.1)";
  1422. }
  1423. else if (noKillLeader) {
  1424. const killsNeeded = this.MINIMUM_KILLS_FOR_LEADER - this.lastKnownKills;
  1425. message = `Nice Kill! Get ${killsNeeded} more kills to become the first Kill Leader!`;
  1426. }
  1427. else {
  1428. message =
  1429. killsToLeader <= 0
  1430. ? "You're the Kill Leader! 👑"
  1431. : `Nice Kill! ${killsToLeader} more to become Kill Leader!`;
  1432. }
  1433. const span = this.encouragementElement.querySelector("span");
  1434. if (span)
  1435. span.textContent = message;
  1436. this.encouragementElement.style.display = "block";
  1437. this.encouragementElement.style.animation = "fadeInOut 3s forwards";
  1438. setTimeout(() => {
  1439. if (this.encouragementElement) {
  1440. this.encouragementElement.style.display = "none";
  1441. // Reset colors
  1442. this.encouragementElement.style.borderColor = "#00ff00";
  1443. this.encouragementElement.style.color = "#00ff00";
  1444. this.encouragementElement.style.background = "rgba(0, 255, 0, 0.1)";
  1445. }
  1446. }, 7000);
  1447. }
  1448. isKillLeader() {
  1449. const killLeaderNameElement = document.querySelector("#ui-kill-leader-name");
  1450. return this.kxsClient.getPlayerName() === (killLeaderNameElement === null || killLeaderNameElement === void 0 ? void 0 : killLeaderNameElement.textContent);
  1451. }
  1452. update(myKills) {
  1453. if (!this.kxsClient.isKillLeaderTrackerEnabled)
  1454. return;
  1455. const killLeaderElement = document.querySelector("#ui-kill-leader-count");
  1456. this.killLeaderKillCount = parseInt((killLeaderElement === null || killLeaderElement === void 0 ? void 0 : killLeaderElement.textContent) || "0", 10);
  1457. if (myKills > this.lastKnownKills) {
  1458. if (this.killLeaderKillCount === 0) {
  1459. // Pas encore de kill leader, encourager le joueur à atteindre 3 kills
  1460. this.showEncouragement(0, false, true);
  1461. }
  1462. else if (this.killLeaderKillCount < this.MINIMUM_KILLS_FOR_LEADER) {
  1463. // Ne rien faire si le kill leader n'a pas atteint le minimum requis
  1464. return;
  1465. }
  1466. else if (this.isKillLeader()) {
  1467. this.showEncouragement(0);
  1468. this.wasKillLeader = true;
  1469. }
  1470. else {
  1471. const killsNeeded = this.killLeaderKillCount + 1 - myKills;
  1472. this.showEncouragement(killsNeeded);
  1473. }
  1474. }
  1475. else if (this.wasKillLeader && !this.isKillLeader()) {
  1476. // Détroné
  1477. this.showEncouragement(0, true);
  1478. this.wasKillLeader = false;
  1479. }
  1480. this.lastKnownKills = myKills;
  1481. }
  1482. }
  1483.  
  1484.  
  1485. ;// ./src/HUD/GridSystem.ts
  1486. class GridSystem {
  1487. constructor() {
  1488. this.gridSize = 20; // Size of each grid cell
  1489. this.snapThreshold = 15; // Distance in pixels to trigger snap
  1490. this.gridVisible = false;
  1491. this.magneticEdges = true;
  1492. this.gridContainer = this.createGridOverlay();
  1493. this.setupKeyBindings();
  1494. }
  1495. createGridOverlay() {
  1496. const container = document.createElement("div");
  1497. container.id = "grid-overlay";
  1498. Object.assign(container.style, {
  1499. position: "fixed",
  1500. top: "0",
  1501. left: "0",
  1502. width: "100%",
  1503. height: "100%",
  1504. pointerEvents: "none",
  1505. zIndex: "9999",
  1506. display: "none",
  1507. opacity: "0.2",
  1508. });
  1509. // Create vertical lines
  1510. for (let x = this.gridSize; x < window.innerWidth; x += this.gridSize) {
  1511. const vLine = document.createElement("div");
  1512. Object.assign(vLine.style, {
  1513. position: "absolute",
  1514. left: `${x}px`,
  1515. top: "0",
  1516. width: "1px",
  1517. height: "100%",
  1518. backgroundColor: "#4CAF50",
  1519. });
  1520. container.appendChild(vLine);
  1521. }
  1522. // Create horizontal lines
  1523. for (let y = this.gridSize; y < window.innerHeight; y += this.gridSize) {
  1524. const hLine = document.createElement("div");
  1525. Object.assign(hLine.style, {
  1526. position: "absolute",
  1527. left: "0",
  1528. top: `${y}px`,
  1529. width: "100%",
  1530. height: "1px",
  1531. backgroundColor: "#4CAF50",
  1532. });
  1533. container.appendChild(hLine);
  1534. }
  1535. document.body.appendChild(container);
  1536. return container;
  1537. }
  1538. setupKeyBindings() {
  1539. document.addEventListener("keydown", (e) => {
  1540. if (e.key === "g" && e.altKey) {
  1541. this.toggleGrid();
  1542. }
  1543. });
  1544. }
  1545. toggleGrid() {
  1546. this.gridVisible = !this.gridVisible;
  1547. this.gridContainer.style.display = this.gridVisible ? "block" : "none";
  1548. }
  1549. snapToGrid(element, x, y) {
  1550. const rect = element.getBoundingClientRect();
  1551. const elementWidth = rect.width;
  1552. const elementHeight = rect.height;
  1553. // Snap to grid
  1554. let snappedX = Math.round(x / this.gridSize) * this.gridSize;
  1555. let snappedY = Math.round(y / this.gridSize) * this.gridSize;
  1556. // Edge snapping
  1557. if (this.magneticEdges) {
  1558. const screenEdges = {
  1559. left: 0,
  1560. right: window.innerWidth - elementWidth,
  1561. center: (window.innerWidth - elementWidth) / 2,
  1562. top: 0,
  1563. bottom: window.innerHeight - elementHeight,
  1564. middle: (window.innerHeight - elementHeight) / 2,
  1565. };
  1566. // Snap to horizontal edges
  1567. if (Math.abs(x - screenEdges.left) < this.snapThreshold) {
  1568. snappedX = screenEdges.left;
  1569. }
  1570. else if (Math.abs(x - screenEdges.right) < this.snapThreshold) {
  1571. snappedX = screenEdges.right;
  1572. }
  1573. else if (Math.abs(x - screenEdges.center) < this.snapThreshold) {
  1574. snappedX = screenEdges.center;
  1575. }
  1576. // Snap to vertical edges
  1577. if (Math.abs(y - screenEdges.top) < this.snapThreshold) {
  1578. snappedY = screenEdges.top;
  1579. }
  1580. else if (Math.abs(y - screenEdges.bottom) < this.snapThreshold) {
  1581. snappedY = screenEdges.bottom;
  1582. }
  1583. else if (Math.abs(y - screenEdges.middle) < this.snapThreshold) {
  1584. snappedY = screenEdges.middle;
  1585. }
  1586. }
  1587. return { x: snappedX, y: snappedY };
  1588. }
  1589. highlightNearestGridLine(x, y) {
  1590. if (!this.gridVisible)
  1591. return;
  1592. // Remove existing highlights
  1593. const highlights = document.querySelectorAll(".grid-highlight");
  1594. highlights.forEach((h) => h.remove());
  1595. // Create highlight for nearest vertical line
  1596. const nearestX = Math.round(x / this.gridSize) * this.gridSize;
  1597. if (Math.abs(x - nearestX) < this.snapThreshold) {
  1598. const vHighlight = document.createElement("div");
  1599. Object.assign(vHighlight.style, {
  1600. position: "absolute",
  1601. left: `${nearestX}px`,
  1602. top: "0",
  1603. width: "2px",
  1604. height: "100%",
  1605. backgroundColor: "#FFD700",
  1606. zIndex: "10000",
  1607. pointerEvents: "none",
  1608. });
  1609. vHighlight.classList.add("grid-highlight");
  1610. this.gridContainer.appendChild(vHighlight);
  1611. }
  1612. // Create highlight for nearest horizontal line
  1613. const nearestY = Math.round(y / this.gridSize) * this.gridSize;
  1614. if (Math.abs(y - nearestY) < this.snapThreshold) {
  1615. const hHighlight = document.createElement("div");
  1616. Object.assign(hHighlight.style, {
  1617. position: "absolute",
  1618. left: "0",
  1619. top: `${nearestY}px`,
  1620. width: "100%",
  1621. height: "2px",
  1622. backgroundColor: "#FFD700",
  1623. zIndex: "10000",
  1624. pointerEvents: "none",
  1625. });
  1626. hHighlight.classList.add("grid-highlight");
  1627. this.gridContainer.appendChild(hHighlight);
  1628. }
  1629. }
  1630. }
  1631.  
  1632.  
  1633. ;// ./src/SERVER/DiscordTracking.ts
  1634. var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
  1635. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  1636. return new (P || (P = Promise))(function (resolve, reject) {
  1637. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  1638. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  1639. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  1640. step((generator = generator.apply(thisArg, _arguments || [])).next());
  1641. });
  1642. };
  1643.  
  1644. const stuff_emojis = {
  1645. main_weapon: "🔫",
  1646. secondary_weapon: "🔫",
  1647. grenades: "💣",
  1648. melees: "🔪",
  1649. soda: "🥤",
  1650. medkit: "🩹",
  1651. bandage: "🩹",
  1652. pills: "💊",
  1653. backpack: "🎒",
  1654. chest: "📦",
  1655. helmet: "⛑️"
  1656. };
  1657. class WebhookValidator {
  1658. static isValidWebhookUrl(url) {
  1659. return url.startsWith("https://");
  1660. }
  1661. static isWebhookAlive(webhookUrl) {
  1662. return __awaiter(this, void 0, void 0, function* () {
  1663. try {
  1664. // First check if the URL format is valid
  1665. if (!this.isValidWebhookUrl(webhookUrl)) {
  1666. throw new Error("Invalid webhook URL format");
  1667. }
  1668. // Test the webhook with a GET request (Discord allows GET on webhooks)
  1669. const response = yield fetch(webhookUrl, {
  1670. method: "GET",
  1671. headers: {
  1672. "Content-Type": "application/json",
  1673. },
  1674. });
  1675. // Discord returns 200 for valid webhooks
  1676. return response.status === 200;
  1677. }
  1678. catch (error) {
  1679. return false;
  1680. }
  1681. });
  1682. }
  1683. static testWebhook(webhookUrl) {
  1684. return __awaiter(this, void 0, void 0, function* () {
  1685. try {
  1686. if (!webhookUrl) {
  1687. return {
  1688. isValid: false,
  1689. message: "Please enter a webhook URL",
  1690. };
  1691. }
  1692. if (!this.isValidWebhookUrl(webhookUrl)) {
  1693. return {
  1694. isValid: false,
  1695. message: "Invalid Discord webhook URL format",
  1696. };
  1697. }
  1698. const isAlive = yield this.isWebhookAlive(webhookUrl);
  1699. return {
  1700. isValid: isAlive,
  1701. message: isAlive
  1702. ? "Webhook is valid and working!"
  1703. : "Webhook is not responding or has been deleted",
  1704. };
  1705. }
  1706. catch (error) {
  1707. return {
  1708. isValid: false,
  1709. message: "Error testing webhook connection",
  1710. };
  1711. }
  1712. });
  1713. }
  1714. }
  1715. class DiscordTracking {
  1716. constructor(kxsClient, webhookUrl) {
  1717. this.kxsClient = kxsClient;
  1718. this.webhookUrl = webhookUrl;
  1719. }
  1720. setWebhookUrl(webhookUrl) {
  1721. this.webhookUrl = webhookUrl;
  1722. }
  1723. validateCurrentWebhook() {
  1724. return __awaiter(this, void 0, void 0, function* () {
  1725. return WebhookValidator.isWebhookAlive(this.webhookUrl);
  1726. });
  1727. }
  1728. sendWebhookMessage(message) {
  1729. return __awaiter(this, void 0, void 0, function* () {
  1730. if (!WebhookValidator.isValidWebhookUrl(this.webhookUrl)) {
  1731. return;
  1732. }
  1733. this.kxsClient.nm.showNotification("Sending Discord message...", "info", 2300);
  1734. try {
  1735. const response = yield fetch(this.webhookUrl, {
  1736. method: "POST",
  1737. headers: {
  1738. "Content-Type": "application/json",
  1739. },
  1740. body: JSON.stringify(message),
  1741. });
  1742. if (!response.ok) {
  1743. throw new Error(`Discord Webhook Error: ${response.status}`);
  1744. }
  1745. }
  1746. catch (error) {
  1747. this.kxsClient.logger.error("Error sending Discord message:", error);
  1748. }
  1749. });
  1750. }
  1751. getEmbedColor(isWin) {
  1752. return isWin ? 0x2ecc71 : 0xe74c3c; // Green for victory, red for defeat
  1753. }
  1754. trackGameEnd(result) {
  1755. return __awaiter(this, void 0, void 0, function* () {
  1756. const title = result.isWin
  1757. ? "🏆 VICTORY ROYALE!"
  1758. : `${result.position} - Game Over`;
  1759. const embed = {
  1760. title,
  1761. description: `${result.username}'s Match`,
  1762. color: this.getEmbedColor(result.isWin),
  1763. fields: [
  1764. {
  1765. name: "💀 Eliminations",
  1766. value: result.kills.toString(),
  1767. inline: true,
  1768. },
  1769. ],
  1770. };
  1771. if (result.duration) {
  1772. embed.fields.push({
  1773. name: "⏱️ Duration",
  1774. value: result.duration,
  1775. inline: true,
  1776. });
  1777. }
  1778. if (result.damageDealt) {
  1779. embed.fields.push({
  1780. name: "💥 Damage Dealt",
  1781. value: Math.round(result.damageDealt).toString(),
  1782. inline: true,
  1783. });
  1784. }
  1785. if (result.damageTaken) {
  1786. embed.fields.push({
  1787. name: "💢 Damage Taken",
  1788. value: Math.round(result.damageTaken).toString(),
  1789. inline: true,
  1790. });
  1791. }
  1792. if (result.username) {
  1793. embed.fields.push({
  1794. name: "📝 Username",
  1795. value: result.username,
  1796. inline: true,
  1797. });
  1798. }
  1799. if (result.stuff) {
  1800. for (const [key, value] of Object.entries(result.stuff)) {
  1801. if (value) {
  1802. embed.fields.push({
  1803. name: `${stuff_emojis[key]} ${key.replace("_", " ").toUpperCase()}`,
  1804. value,
  1805. inline: true,
  1806. });
  1807. }
  1808. }
  1809. }
  1810. const message = {
  1811. username: "KxsClient",
  1812. avatar_url: kxs_logo,
  1813. content: result.isWin ? "🎉 New Victory!" : "Match Ended",
  1814. embeds: [embed],
  1815. };
  1816. yield this.sendWebhookMessage(message);
  1817. });
  1818. }
  1819. }
  1820.  
  1821.  
  1822. ;// ./src/FUNC/StatsParser.ts
  1823. class StatsParser {
  1824. static cleanNumber(str) {
  1825. return parseInt(str.replace(/[^\d.-]/g, "")) || 0;
  1826. }
  1827. /**
  1828. * Extract the full duration string including the unit
  1829. */
  1830. static extractDuration(str) {
  1831. const match = str.match(/(\d+\s*[smh])/i);
  1832. return match ? match[1].trim() : "0s";
  1833. }
  1834. static parse(statsText, rankContent) {
  1835. let stats = {
  1836. username: "Player",
  1837. kills: 0,
  1838. damageDealt: 0,
  1839. damageTaken: 0,
  1840. duration: "",
  1841. position: "#unknown",
  1842. };
  1843. // Handle developer format
  1844. const devPattern = /Developer.*?Kills(\d+).*?Damage Dealt(\d+).*?Damage Taken(\d+).*?Survived(\d+\s*[smh])/i;
  1845. const devMatch = statsText.match(devPattern);
  1846. if (devMatch) {
  1847. return {
  1848. username: "Player",
  1849. kills: this.cleanNumber(devMatch[1]),
  1850. damageDealt: this.cleanNumber(devMatch[2]),
  1851. damageTaken: this.cleanNumber(devMatch[3]),
  1852. duration: devMatch[4].trim(), // Keep the full duration string with unit
  1853. position: rankContent.replace("##", "#"),
  1854. };
  1855. }
  1856. // Handle template format
  1857. const templatePattern = /%username%.*?Kills%kills_number%.*?Dealt%number_dealt%.*?Taken%damage_taken%.*?Survived%duration%/;
  1858. const templateMatch = statsText.match(templatePattern);
  1859. if (templateMatch) {
  1860. const parts = statsText.split(/Kills|Dealt|Taken|Survived/);
  1861. if (parts.length >= 5) {
  1862. return {
  1863. username: parts[0].trim(),
  1864. kills: this.cleanNumber(parts[1]),
  1865. damageDealt: this.cleanNumber(parts[2]),
  1866. damageTaken: this.cleanNumber(parts[3]),
  1867. duration: this.extractDuration(parts[4]), // Extract full duration with unit
  1868. position: rankContent.replace("##", "#"),
  1869. };
  1870. }
  1871. }
  1872. // Generic parsing as fallback
  1873. const usernameMatch = statsText.match(/^([^0-9]+)/);
  1874. if (usernameMatch) {
  1875. stats.username = usernameMatch[1].trim();
  1876. }
  1877. const killsMatch = statsText.match(/Kills[^0-9]*(\d+)/i);
  1878. if (killsMatch) {
  1879. stats.kills = this.cleanNumber(killsMatch[1]);
  1880. }
  1881. const dealtMatch = statsText.match(/Dealt[^0-9]*(\d+)/i);
  1882. if (dealtMatch) {
  1883. stats.damageDealt = this.cleanNumber(dealtMatch[1]);
  1884. }
  1885. const takenMatch = statsText.match(/Taken[^0-9]*(\d+)/i);
  1886. if (takenMatch) {
  1887. stats.damageTaken = this.cleanNumber(takenMatch[1]);
  1888. }
  1889. // Extract survival time with unit
  1890. const survivalMatch = statsText.match(/Survived[^0-9]*(\d+\s*[smh])/i);
  1891. if (survivalMatch) {
  1892. stats.duration = survivalMatch[1].trim();
  1893. }
  1894. stats.position = rankContent.replace("##", "#");
  1895. return stats;
  1896. }
  1897. }
  1898.  
  1899.  
  1900. // EXTERNAL MODULE: ./node_modules/semver/functions/gt.js
  1901. var gt = __webpack_require__(580);
  1902. var gt_default = /*#__PURE__*/__webpack_require__.n(gt);
  1903. ;// ./package.json
  1904. const package_namespaceObject = {"rE":"2.1.4"};
  1905. ;// ./src/FUNC/UpdateChecker.ts
  1906. var UpdateChecker_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
  1907. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  1908. return new (P || (P = Promise))(function (resolve, reject) {
  1909. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  1910. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  1911. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  1912. step((generator = generator.apply(thisArg, _arguments || [])).next());
  1913. });
  1914. };
  1915.  
  1916.  
  1917.  
  1918. class UpdateChecker {
  1919. constructor(kxsClient) {
  1920. this.remoteScriptUrl = config_namespaceObject.api_url + "/getLatestVersion";
  1921. this.kxsClient = kxsClient;
  1922. if (this.kxsClient.isAutoUpdateEnabled) {
  1923. this.checkForUpdate();
  1924. }
  1925. }
  1926. downloadScript() {
  1927. return UpdateChecker_awaiter(this, void 0, void 0, function* () {
  1928. try {
  1929. const response = yield fetch(this.remoteScriptUrl, {
  1930. method: "GET",
  1931. headers: {
  1932. "cache-control": "no-cache, no-store, must-revalidate",
  1933. "pragma": "no-cache",
  1934. "expires": "0"
  1935. }
  1936. });
  1937. if (!response.ok) {
  1938. throw new Error("Error downloading script: " + response.statusText);
  1939. }
  1940. const blob = yield response.blob();
  1941. const downloadUrl = window.URL.createObjectURL(blob);
  1942. const downloadLink = document.createElement('a');
  1943. downloadLink.href = downloadUrl;
  1944. downloadLink.download = 'KxsClient.user.js';
  1945. document.body.appendChild(downloadLink);
  1946. downloadLink.click();
  1947. document.body.removeChild(downloadLink);
  1948. window.URL.revokeObjectURL(downloadUrl);
  1949. }
  1950. catch (error) {
  1951. throw new Error("Error during script download: " + error);
  1952. }
  1953. });
  1954. }
  1955. getNewScriptVersion() {
  1956. return UpdateChecker_awaiter(this, void 0, void 0, function* () {
  1957. try {
  1958. const response = yield fetch(this.remoteScriptUrl, {
  1959. method: "GET",
  1960. headers: {
  1961. "cache-control": "no-cache, no-store, must-revalidate",
  1962. "pragma": "no-cache",
  1963. "expires": "0"
  1964. }
  1965. });
  1966. if (!response.ok) {
  1967. throw new Error("Error retrieving remote script: " + response.statusText);
  1968. }
  1969. const scriptContent = yield response.text();
  1970. const versionMatch = scriptContent.match(/\/\/\s*@version\s+([\d.]+)/);
  1971. if (versionMatch && versionMatch[1]) {
  1972. return versionMatch[1];
  1973. }
  1974. else {
  1975. throw new Error("Script version was not found in the file.");
  1976. }
  1977. }
  1978. catch (error) {
  1979. throw new Error("Error retrieving remote script: " + error);
  1980. }
  1981. });
  1982. }
  1983. checkForUpdate() {
  1984. return UpdateChecker_awaiter(this, void 0, void 0, function* () {
  1985. const localScriptVersion = yield this.getCurrentScriptVersion();
  1986. const hostedScriptVersion = yield this.getNewScriptVersion();
  1987. this.hostedScriptVersion = hostedScriptVersion;
  1988. // Vérifie si la version hébergée est supérieure à la version locale
  1989. if (gt_default()(hostedScriptVersion, localScriptVersion)) {
  1990. this.displayUpdateNotification();
  1991. }
  1992. else {
  1993. this.kxsClient.nm.showNotification("Client is up to date", "success", 2300);
  1994. }
  1995. });
  1996. }
  1997. displayUpdateNotification() {
  1998. const modal = document.createElement("div");
  1999. modal.style.position = "fixed";
  2000. modal.style.top = "50%";
  2001. modal.style.left = "50%";
  2002. modal.style.transform = "translate(-50%, -50%)";
  2003. modal.style.backgroundColor = "rgb(250, 250, 250)";
  2004. modal.style.borderRadius = "10px";
  2005. modal.style.padding = "20px";
  2006. modal.style.width = "400px";
  2007. modal.style.boxShadow = "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)";
  2008. modal.style.border = "1px solid rgb(229, 229, 229)";
  2009. const header = document.createElement("div");
  2010. header.style.display = "flex";
  2011. header.style.alignItems = "center";
  2012. header.style.marginBottom = "15px";
  2013. const title = document.createElement("h3");
  2014. title.textContent = "Download Update";
  2015. title.style.margin = "0";
  2016. title.style.fontSize = "16px";
  2017. title.style.fontWeight = "600";
  2018. header.appendChild(title);
  2019. const closeButton = document.createElement("button");
  2020. closeButton.innerHTML = "×";
  2021. closeButton.style.marginLeft = "auto";
  2022. closeButton.style.border = "none";
  2023. closeButton.style.background = "none";
  2024. closeButton.style.fontSize = "20px";
  2025. closeButton.style.cursor = "pointer";
  2026. closeButton.style.padding = "0 5px";
  2027. closeButton.onclick = () => modal.remove();
  2028. header.appendChild(closeButton);
  2029. const content = document.createElement("div");
  2030. content.innerHTML = `A new version of KxsClient is available!<br>
  2031. Locale: ${this.getCurrentScriptVersion()} | On web: ${this.hostedScriptVersion}<br>
  2032. Click the button below to update now.`;
  2033. content.style.marginBottom = "20px";
  2034. content.style.color = "rgb(75, 85, 99)";
  2035. const updateButton = document.createElement("button");
  2036. updateButton.textContent = "Update Now";
  2037. updateButton.style.backgroundColor = "rgb(59, 130, 246)";
  2038. updateButton.style.color = "white";
  2039. updateButton.style.padding = "8px 16px";
  2040. updateButton.style.borderRadius = "6px";
  2041. updateButton.style.border = "none";
  2042. updateButton.style.cursor = "pointer";
  2043. updateButton.style.width = "100%";
  2044. updateButton.onclick = () => UpdateChecker_awaiter(this, void 0, void 0, function* () {
  2045. try {
  2046. yield this.downloadScript();
  2047. this.kxsClient.nm.showNotification("Download started", "success", 2300);
  2048. modal.remove();
  2049. }
  2050. catch (error) {
  2051. this.kxsClient.nm.showNotification("Download failed: " + error.message, "info", 5000);
  2052. }
  2053. });
  2054. modal.appendChild(header);
  2055. modal.appendChild(content);
  2056. modal.appendChild(updateButton);
  2057. document.body.appendChild(modal);
  2058. }
  2059. getCurrentScriptVersion() {
  2060. return package_namespaceObject.rE;
  2061. }
  2062. }
  2063.  
  2064.  
  2065. ;// ./src/SERVER/DiscordRichPresence.ts
  2066.  
  2067. class DiscordWebSocket {
  2068. constructor(kxsClient, token) {
  2069. this.ws = null;
  2070. this.heartbeatInterval = 0;
  2071. this.sequence = null;
  2072. this.isAuthenticated = false;
  2073. this.kxsClient = kxsClient;
  2074. }
  2075. connect() {
  2076. if (this.kxsClient.discordToken === ""
  2077. || this.kxsClient.discordToken === null
  2078. || this.kxsClient.discordToken === undefined) {
  2079. return;
  2080. }
  2081. this.ws = new WebSocket('wss://gateway.discord.gg/?v=9&encoding=json');
  2082. this.ws.onopen = () => {
  2083. this.kxsClient.logger.log('[RichPresence] WebSocket connection established');
  2084. };
  2085. this.ws.onmessage = (event) => {
  2086. const data = JSON.parse(event.data);
  2087. this.handleMessage(data);
  2088. };
  2089. this.ws.onerror = (error) => {
  2090. this.kxsClient.nm.showNotification('WebSocket error: ' + error.type, 'error', 5000);
  2091. };
  2092. this.ws.onclose = () => {
  2093. this.kxsClient.nm.showNotification('Disconnected from Discord gateway', 'info', 5000);
  2094. clearInterval(this.heartbeatInterval);
  2095. this.isAuthenticated = false;
  2096. };
  2097. }
  2098. identify() {
  2099. const payload = {
  2100. op: 2,
  2101. d: {
  2102. token: this.kxsClient.discordToken,
  2103. properties: {
  2104. $os: 'linux',
  2105. $browser: 'chrome',
  2106. $device: 'chrome'
  2107. },
  2108. presence: {
  2109. activities: [{
  2110. name: "KxsClient",
  2111. type: 0,
  2112. application_id: "1321193265533550602",
  2113. assets: {
  2114. large_image: "mp:app-icons/1321193265533550602/bccd2479ec56ed7d4e69fa2fdfb47197.png?size=512",
  2115. large_text: "KxsClient v" + package_namespaceObject.rE,
  2116. }
  2117. }],
  2118. status: 'online',
  2119. afk: false
  2120. }
  2121. }
  2122. };
  2123. this.send(payload);
  2124. }
  2125. handleMessage(data) {
  2126. switch (data.op) {
  2127. case 10: // Hello
  2128. const { heartbeat_interval } = data.d;
  2129. this.startHeartbeat(heartbeat_interval);
  2130. this.identify();
  2131. break;
  2132. case 11: // Heartbeat ACK
  2133. this.kxsClient.logger.log('[RichPresence] Heartbeat acknowledged');
  2134. break;
  2135. case 0: // Dispatch
  2136. this.sequence = data.s;
  2137. if (data.t === 'READY') {
  2138. this.isAuthenticated = true;
  2139. this.kxsClient.nm.showNotification('Started Discord RPC', 'success', 3000);
  2140. }
  2141. break;
  2142. }
  2143. }
  2144. startHeartbeat(interval) {
  2145. this.heartbeatInterval = setInterval(() => {
  2146. this.send({
  2147. op: 1,
  2148. d: this.sequence
  2149. });
  2150. }, interval);
  2151. }
  2152. send(data) {
  2153. var _a;
  2154. if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
  2155. this.ws.send(JSON.stringify(data));
  2156. }
  2157. }
  2158. disconnect() {
  2159. if (this.ws) {
  2160. clearInterval(this.heartbeatInterval);
  2161. this.ws.close();
  2162. }
  2163. }
  2164. }
  2165.  
  2166.  
  2167. ;// ./src/HUD/MOD/NotificationManager.ts
  2168. class NotificationManager {
  2169. constructor() {
  2170. this.notifications = [];
  2171. this.NOTIFICATION_HEIGHT = 65; // Height + margin
  2172. this.NOTIFICATION_MARGIN = 10;
  2173. this.addGlobalStyles();
  2174. }
  2175. static getInstance() {
  2176. if (!NotificationManager.instance) {
  2177. NotificationManager.instance = new NotificationManager();
  2178. }
  2179. return NotificationManager.instance;
  2180. }
  2181. addGlobalStyles() {
  2182. const styleSheet = document.createElement("style");
  2183. styleSheet.textContent = `
  2184. @keyframes slideIn {
  2185. 0% { transform: translateX(-120%); opacity: 0; }
  2186. 50% { transform: translateX(10px); opacity: 0.8; }
  2187. 100% { transform: translateX(0); opacity: 1; }
  2188. }
  2189. @keyframes slideOut {
  2190. 0% { transform: translateX(0); opacity: 1; }
  2191. 50% { transform: translateX(10px); opacity: 0.8; }
  2192. 100% { transform: translateX(-120%); opacity: 0; }
  2193. }
  2194. @keyframes slideLeft {
  2195. from { transform-origin: right; transform: scaleX(1); }
  2196. to { transform-origin: right; transform: scaleX(0); }
  2197. }
  2198. @keyframes bounce {
  2199. 0%, 100% { transform: scale(1); }
  2200. 50% { transform: scale(1.1); }
  2201. }
  2202. `;
  2203. document.head.appendChild(styleSheet);
  2204. }
  2205. updateNotificationPositions() {
  2206. this.notifications.forEach((notification, index) => {
  2207. const topPosition = 20 + (index * this.NOTIFICATION_HEIGHT);
  2208. notification.style.top = `${topPosition}px`;
  2209. });
  2210. }
  2211. removeNotification(notification) {
  2212. const index = this.notifications.indexOf(notification);
  2213. if (index > -1) {
  2214. this.notifications.splice(index, 1);
  2215. this.updateNotificationPositions();
  2216. }
  2217. }
  2218. getIconConfig(type) {
  2219. const configs = {
  2220. success: {
  2221. color: '#4CAF50',
  2222. svg: `<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
  2223. <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
  2224. </svg>`
  2225. },
  2226. error: {
  2227. color: '#F44336',
  2228. svg: `<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
  2229. <path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z"/>
  2230. </svg>`
  2231. },
  2232. info: {
  2233. color: '#FFD700',
  2234. svg: `<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
  2235. <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
  2236. </svg>`
  2237. }
  2238. };
  2239. return configs[type];
  2240. }
  2241. showNotification(message, type, duration = 5000) {
  2242. const notification = document.createElement("div");
  2243. // Base styles
  2244. Object.assign(notification.style, {
  2245. position: "fixed",
  2246. top: "20px",
  2247. left: "20px",
  2248. padding: "12px 20px",
  2249. backgroundColor: "#333333",
  2250. color: "white",
  2251. zIndex: "9999",
  2252. minWidth: "200px",
  2253. borderRadius: "4px",
  2254. display: "flex",
  2255. alignItems: "center",
  2256. gap: "10px",
  2257. transform: "translateX(-120%)",
  2258. opacity: "0",
  2259. boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)"
  2260. });
  2261. // Create icon
  2262. const icon = document.createElement("div");
  2263. Object.assign(icon.style, {
  2264. width: "20px",
  2265. height: "20px",
  2266. display: "flex",
  2267. alignItems: "center",
  2268. justifyContent: "center",
  2269. animation: "bounce 0.5s ease-in-out"
  2270. });
  2271. const iconConfig = this.getIconConfig(type);
  2272. icon.style.color = iconConfig.color;
  2273. icon.innerHTML = iconConfig.svg;
  2274. // Create message
  2275. const messageDiv = document.createElement("div");
  2276. messageDiv.textContent = message;
  2277. messageDiv.style.flex = "1";
  2278. // Create progress bar
  2279. const progressBar = document.createElement("div");
  2280. Object.assign(progressBar.style, {
  2281. height: "4px",
  2282. backgroundColor: "#e6f3ff",
  2283. width: "100%",
  2284. position: "absolute",
  2285. bottom: "0",
  2286. left: "0",
  2287. animation: `slideLeft ${duration}ms linear forwards`
  2288. });
  2289. // Assemble notification
  2290. notification.appendChild(icon);
  2291. notification.appendChild(messageDiv);
  2292. notification.appendChild(progressBar);
  2293. document.body.appendChild(notification);
  2294. // Add to stack and update positions
  2295. this.notifications.push(notification);
  2296. this.updateNotificationPositions();
  2297. // Entrance animation
  2298. requestAnimationFrame(() => {
  2299. notification.style.transition = "all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55)";
  2300. notification.style.animation = "slideIn 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards";
  2301. });
  2302. // Exit animation and cleanup
  2303. setTimeout(() => {
  2304. notification.style.animation = "slideOut 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards";
  2305. setTimeout(() => {
  2306. this.removeNotification(notification);
  2307. notification.remove();
  2308. }, 500);
  2309. }, duration);
  2310. }
  2311. }
  2312.  
  2313.  
  2314. ;// ./src/HUD/ClientSecondaryMenu.ts
  2315.  
  2316. const category = ["ALL", "HUD", "SERVER", "MECHANIC", "MISC"];
  2317. class KxsClientSecondaryMenu {
  2318. constructor(kxsClient) {
  2319. this.searchTerm = '';
  2320. // Fonction pour fermer un sous-menu
  2321. this.closeSubMenu = () => { };
  2322. this.shiftListener = (event) => {
  2323. if (event.key === "Shift" && event.location == 2) {
  2324. this.clearMenu();
  2325. this.toggleMenuVisibility();
  2326. // Ensure options are displayed after loading
  2327. this.filterOptions();
  2328. }
  2329. };
  2330. this.mouseMoveListener = (e) => {
  2331. if (this.isDragging) {
  2332. const x = e.clientX - this.dragOffset.x;
  2333. const y = e.clientY - this.dragOffset.y;
  2334. this.menu.style.transform = 'none';
  2335. this.menu.style.left = `${x}px`;
  2336. this.menu.style.top = `${y}px`;
  2337. }
  2338. };
  2339. this.mouseUpListener = () => {
  2340. this.isDragging = false;
  2341. this.menu.style.cursor = "grab";
  2342. };
  2343. this.kxsClient = kxsClient;
  2344. this.isClientMenuVisible = false;
  2345. this.isDragging = false;
  2346. this.dragOffset = { x: 0, y: 0 };
  2347. this.sections = [];
  2348. this.allOptions = [];
  2349. this.activeCategory = "ALL";
  2350. this.isOpen = false;
  2351. this.menu = document.createElement("div");
  2352. this.initMenu();
  2353. this.addShiftListener();
  2354. this.addDragListeners();
  2355. this.loadOption();
  2356. }
  2357. initMenu() {
  2358. this.menu.id = "kxsClientMenu";
  2359. this.applyMenuStyles();
  2360. this.createHeader();
  2361. this.createGridContainer();
  2362. document.body.appendChild(this.menu);
  2363. this.menu.style.display = "none";
  2364. // Empêcher la propagation des événements souris (clics et molette) vers la page web
  2365. // Utiliser la phase de bouillonnement (bubbling) au lieu de la phase de capture
  2366. // pour permettre aux éléments enfants de recevoir les événements d'abord
  2367. this.menu.addEventListener('click', (e) => {
  2368. e.stopPropagation();
  2369. });
  2370. this.menu.addEventListener('wheel', (e) => {
  2371. e.stopPropagation();
  2372. });
  2373. // Nous ne gérons pas mousedown et mouseup ici car ils sont gérés dans addDragListeners()
  2374. }
  2375. applyMenuStyles() {
  2376. // Styles par défaut (desktop/tablette)
  2377. const defaultStyles = {
  2378. backgroundColor: "rgba(17, 24, 39, 0.95)",
  2379. padding: "20px",
  2380. borderRadius: "12px",
  2381. boxShadow: "0 4px 20px rgba(0, 0, 0, 0.8)",
  2382. zIndex: "10001",
  2383. width: "800px",
  2384. fontFamily: "'Segoe UI', Arial, sans-serif",
  2385. color: "#fff",
  2386. maxHeight: "80vh",
  2387. overflowY: "auto",
  2388. overflowX: "hidden", // Prevent horizontal scrolling
  2389. position: "fixed",
  2390. top: "10%",
  2391. left: "50%",
  2392. transform: "translateX(-50%)",
  2393. display: "none",
  2394. boxSizing: "border-box", // Include padding in width calculation
  2395. };
  2396. // Styles réduits pour mobile
  2397. const mobileStyles = {
  2398. padding: "6px",
  2399. borderRadius: "7px",
  2400. width: "78vw",
  2401. maxWidth: "84vw",
  2402. fontSize: "10px",
  2403. maxHeight: "60vh",
  2404. top: "4%",
  2405. left: "50%",
  2406. };
  2407. Object.assign(this.menu.style, defaultStyles);
  2408. if (this.kxsClient.isMobile && this.kxsClient.isMobile()) {
  2409. Object.assign(this.menu.style, mobileStyles);
  2410. }
  2411. }
  2412. blockMousePropagation(element, preventDefault = true) {
  2413. ['click', 'mousedown', 'mouseup', 'dblclick', 'contextmenu', 'wheel'].forEach(eventType => {
  2414. element.addEventListener(eventType, (e) => {
  2415. e.stopPropagation();
  2416. if (preventDefault && (eventType === 'contextmenu' || eventType === 'wheel' || element.tagName !== 'INPUT')) {
  2417. e.preventDefault();
  2418. }
  2419. }, false);
  2420. });
  2421. }
  2422. createHeader() {
  2423. const header = document.createElement("div");
  2424. // Détection mobile pour styles réduits
  2425. const isMobile = this.kxsClient.isMobile && this.kxsClient.isMobile();
  2426. const logoSize = isMobile ? 16 : 24;
  2427. const titleFontSize = isMobile ? 12 : 20;
  2428. const headerGap = isMobile ? 4 : 10;
  2429. const headerMarginBottom = isMobile ? 8 : 20;
  2430. const closeBtnPadding = isMobile ? 2 : 6;
  2431. const closeBtnFontSize = isMobile ? 12 : 18;
  2432. header.style.marginBottom = `${headerMarginBottom}px`;
  2433. header.innerHTML = `
  2434. <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: ${isMobile ? 7 : 15}px; width: 100%; box-sizing: border-box;">
  2435. <div style="display: flex; align-items: center; gap: ${headerGap}px;">
  2436. <img src="${kxs_logo}"
  2437. alt="Logo" style="width: ${logoSize}px; height: ${logoSize}px;">
  2438. <span style="font-size: ${titleFontSize}px; font-weight: bold;">KXS CLIENT</span>
  2439. </div>
  2440. <div style="display: flex; gap: ${headerGap}px;">
  2441. <button style="
  2442. padding: ${closeBtnPadding}px;
  2443. background: none;
  2444. border: none;
  2445. color: white;
  2446. cursor: pointer;
  2447. font-size: ${closeBtnFontSize}px;
  2448. ">×</button>
  2449. </div>
  2450. </div>
  2451. <div style="display: flex; flex-direction: column; gap: 10px; margin-bottom: 15px; width: 100%; box-sizing: border-box;">
  2452. <div style="display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 5px;">
  2453. ${category.map(cat => `
  2454. <button class="category-btn" data-category="${cat}" style="
  2455. padding: ${isMobile ? '2px 6px' : '6px 16px'};
  2456. background: ${this.activeCategory === cat ? '#3B82F6' : 'rgba(55, 65, 81, 0.8)'};
  2457. border: none;
  2458. border-radius: ${isMobile ? '3px' : '6px'};
  2459. color: white;
  2460. cursor: pointer;
  2461. font-size: ${isMobile ? '9px' : '14px'};
  2462. transition: background 0.2s;
  2463. ">${cat}</button>
  2464. `).join('')}
  2465. </div>
  2466. <div style="display: flex; width: 100%; box-sizing: border-box;">
  2467. <div style="position: relative; width: 100%; box-sizing: border-box;">
  2468. <input type="text" id="kxsSearchInput" placeholder="Search options..." style="
  2469. width: 100%;
  2470. padding: ${isMobile ? '3px 5px 3px 20px' : '8px 12px 8px 32px'};
  2471. background: rgba(55, 65, 81, 0.8);
  2472. border: none;
  2473. border-radius: ${isMobile ? '3px' : '6px'};
  2474. color: white;
  2475. font-size: ${isMobile ? '9px' : '14px'};
  2476. outline: none;
  2477. box-sizing: border-box;
  2478. ">
  2479. <div style="
  2480. position: absolute;
  2481. left: ${isMobile ? '4px' : '10px'};
  2482. top: 50%;
  2483. transform: translateY(-50%);
  2484. width: ${isMobile ? '9px' : '14px'};
  2485. height: ${isMobile ? '9px' : '14px'};
  2486. ">
  2487. <svg fill="#ffffff" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
  2488. <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
  2489. </svg>
  2490. </div>
  2491. </div>
  2492. </div>
  2493. </div>
  2494. `;
  2495. header.querySelectorAll('.category-btn').forEach(btn => {
  2496. this.blockMousePropagation(btn);
  2497. btn.addEventListener('click', (e) => {
  2498. const category = e.target.dataset.category;
  2499. if (category) {
  2500. this.setActiveCategory(category);
  2501. }
  2502. });
  2503. });
  2504. const closeButton = header.querySelector('button');
  2505. closeButton === null || closeButton === void 0 ? void 0 : closeButton.addEventListener('click', () => {
  2506. this.toggleMenuVisibility();
  2507. });
  2508. const searchInput = header.querySelector('#kxsSearchInput');
  2509. if (searchInput) {
  2510. this.blockMousePropagation(searchInput, false);
  2511. // Gestionnaire pour mettre à jour la recherche
  2512. searchInput.addEventListener('input', (e) => {
  2513. this.searchTerm = e.target.value.toLowerCase();
  2514. this.filterOptions();
  2515. });
  2516. // Prevent keys from being interpreted by the game
  2517. // We only block the propagation of keyboard events, except for special keys
  2518. ['keydown', 'keyup', 'keypress'].forEach(eventType => {
  2519. searchInput.addEventListener(eventType, (e) => {
  2520. const keyEvent = e;
  2521. // Don't block special keys (Escape, Shift)
  2522. if (keyEvent.key === 'Escape' || (keyEvent.key === 'Shift' && keyEvent.location === 2)) {
  2523. return; // Let the event propagate normally
  2524. }
  2525. // Block propagation for all other keys
  2526. e.stopPropagation();
  2527. });
  2528. });
  2529. // Éviter que la barre de recherche ne reprenne automatiquement le focus
  2530. // lorsque l'utilisateur interagit avec un autre champ de texte
  2531. searchInput.addEventListener('blur', (e) => {
  2532. // Ne pas reprendre le focus si l'utilisateur clique sur un autre input
  2533. const newFocusElement = e.relatedTarget;
  2534. if (newFocusElement && (newFocusElement.tagName === 'INPUT' || newFocusElement.tagName === 'TEXTAREA')) {
  2535. // L'utilisateur a cliqué sur un autre champ de texte, ne pas reprendre le focus
  2536. return;
  2537. }
  2538. // Pour les autres cas, seulement si aucun autre élément n'a le focus
  2539. setTimeout(() => {
  2540. const activeElement = document.activeElement;
  2541. if (this.isClientMenuVisible &&
  2542. activeElement &&
  2543. activeElement !== searchInput &&
  2544. activeElement.tagName !== 'INPUT' &&
  2545. activeElement.tagName !== 'TEXTAREA') {
  2546. searchInput.focus();
  2547. }
  2548. }, 100);
  2549. });
  2550. }
  2551. this.menu.appendChild(header);
  2552. }
  2553. clearMenu() {
  2554. const gridContainer = document.getElementById('kxsMenuGrid');
  2555. if (gridContainer) {
  2556. gridContainer.innerHTML = '';
  2557. }
  2558. // Reset search term when clearing menu
  2559. this.searchTerm = '';
  2560. const searchInput = document.getElementById('kxsSearchInput');
  2561. if (searchInput) {
  2562. searchInput.value = '';
  2563. }
  2564. }
  2565. loadOption() {
  2566. // Clear existing options to avoid duplicates
  2567. this.allOptions = [];
  2568. let HUD = this.addSection("HUD", 'HUD');
  2569. let MECHANIC = this.addSection("MECHANIC", 'MECHANIC');
  2570. let SERVER = this.addSection("SERVER", 'SERVER');
  2571. let MISC = this.addSection("MISC", 'MISC');
  2572. this.addOption(SERVER, {
  2573. label: "Kxs Network",
  2574. value: true,
  2575. category: "SERVER",
  2576. type: "sub",
  2577. icon: '<svg fill="#000000" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>network</title> <path d="M27 21.75c-0.795 0.004-1.538 0.229-2.169 0.616l0.018-0.010-2.694-2.449c0.724-1.105 1.154-2.459 1.154-3.913 0-1.572-0.503-3.027-1.358-4.212l0.015 0.021 3.062-3.062c0.57 0.316 1.249 0.503 1.971 0.508h0.002c2.347 0 4.25-1.903 4.25-4.25s-1.903-4.25-4.25-4.25c-2.347 0-4.25 1.903-4.25 4.25v0c0.005 0.724 0.193 1.403 0.519 1.995l-0.011-0.022-3.062 3.062c-1.147-0.84-2.587-1.344-4.144-1.344-0.868 0-1.699 0.157-2.467 0.443l0.049-0.016-0.644-1.17c0.726-0.757 1.173-1.787 1.173-2.921 0-2.332-1.891-4.223-4.223-4.223s-4.223 1.891-4.223 4.223c0 2.332 1.891 4.223 4.223 4.223 0.306 0 0.605-0.033 0.893-0.095l-0.028 0.005 0.642 1.166c-1.685 1.315-2.758 3.345-2.758 5.627 0 0.605 0.076 1.193 0.218 1.754l-0.011-0.049-0.667 0.283c-0.78-0.904-1.927-1.474-3.207-1.474-2.334 0-4.226 1.892-4.226 4.226s1.892 4.226 4.226 4.226c2.334 0 4.226-1.892 4.226-4.226 0-0.008-0-0.017-0-0.025v0.001c-0.008-0.159-0.023-0.307-0.046-0.451l0.003 0.024 0.667-0.283c1.303 2.026 3.547 3.349 6.1 3.349 1.703 0 3.268-0.589 4.503-1.574l-0.015 0.011 2.702 2.455c-0.258 0.526-0.41 1.144-0.414 1.797v0.001c0 2.347 1.903 4.25 4.25 4.25s4.25-1.903 4.25-4.25c0-2.347-1.903-4.25-4.25-4.25v0zM8.19 5c0-0.966 0.784-1.75 1.75-1.75s1.75 0.784 1.75 1.75c0 0.966-0.784 1.75-1.75 1.75v0c-0.966-0.001-1.749-0.784-1.75-1.75v-0zM5 22.42c-0.966-0.001-1.748-0.783-1.748-1.749s0.783-1.749 1.749-1.749c0.966 0 1.748 0.782 1.749 1.748v0c-0.001 0.966-0.784 1.749-1.75 1.75h-0zM27 3.25c0.966 0 1.75 0.784 1.75 1.75s-0.784 1.75-1.75 1.75c-0.966 0-1.75-0.784-1.75-1.75v0c0.001-0.966 0.784-1.749 1.75-1.75h0zM11.19 16c0-0.001 0-0.002 0-0.003 0-2.655 2.152-4.807 4.807-4.807 1.328 0 2.53 0.539 3.4 1.409l0.001 0.001 0.001 0.001c0.87 0.87 1.407 2.072 1.407 3.399 0 2.656-2.153 4.808-4.808 4.808s-4.808-2.153-4.808-4.808c0-0 0-0 0-0v0zM27 27.75c-0.966 0-1.75-0.784-1.75-1.75s0.784-1.75 1.75-1.75c0.966 0 1.75 0.784 1.75 1.75v0c-0.001 0.966-0.784 1.749-1.75 1.75h-0z"></path> </g></svg>',
  2578. fields: [
  2579. {
  2580. label: "Spoof Nickname",
  2581. category: "SERVER",
  2582. icon: '<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>hacker-solid</title> <g id="Layer_2" data-name="Layer 2"> <g id="invisible_box" data-name="invisible box"> <rect width="48" height="48" fill="none"></rect> </g> <g id="Q3_icons" data-name="Q3 icons"> <g> <path d="M24,30a60.3,60.3,0,0,1-13-1.3L7,27.6V40.2a1.9,1.9,0,0,0,1.5,1.9l12,2.9a2.4,2.4,0,0,0,2.1-.8L24,42.5l1.4,1.7A2.1,2.1,0,0,0,27,45h.5l12-2.9A1.9,1.9,0,0,0,41,40.2V27.6l-4,1.1A60.3,60.3,0,0,1,24,30Zm-7,8c-2,0-4-1.9-4-3s2-1,4-1,4,.9,4,2S19,38,17,38Zm14,0c-2,0-4-.9-4-2s2-2,4-2,4-.1,4,1S33,38,31,38Z"></path> <path d="M39.4,16,37.3,6.2A4,4,0,0,0,33.4,3H29.1a3.9,3.9,0,0,0-3.4,1.9L24,7.8,22.3,4.9A3.9,3.9,0,0,0,18.9,3H14.6a4,4,0,0,0-3.9,3.2L8.6,16C4.5,17.3,2,19,2,21c0,3.9,9.8,7,22,7s22-3.1,22-7C46,19,43.5,17.3,39.4,16Z"></path> </g> </g> </g> </g></svg>',
  2583. type: "toggle",
  2584. value: this.kxsClient.kxsNetworkSettings.nickname_anonymized,
  2585. onChange: () => {
  2586. this.kxsClient.kxsNetworkSettings.nickname_anonymized = !this.kxsClient.kxsNetworkSettings.nickname_anonymized;
  2587. this.kxsClient.updateLocalStorage();
  2588. }
  2589. },
  2590. {
  2591. label: "Voice Chat",
  2592. value: this.kxsClient.isVoiceChatEnabled,
  2593. icon: '<svg fill="#000000" viewBox="0 0 32 32" id="icon" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <defs> <style> .cls-1 { fill: none; } </style> </defs> <path d="M26,30H24V27H20a5.0055,5.0055,0,0,1-5-5V20.7207l-2.3162-.772a1,1,0,0,1-.5412-1.4631L15,13.7229V11a9.01,9.01,0,0,1,9-9h5V4H24a7.0078,7.0078,0,0,0-7,7v3a.9991.9991,0,0,1-.1426.5144l-2.3586,3.9312,1.8174.6057A1,1,0,0,1,17,20v2a3.0033,3.0033,0,0,0,3,3h5a1,1,0,0,1,1,1Z"></path> <rect x="19" y="12" width="4" height="2"></rect> <path d="M9.3325,25.2168a7.0007,7.0007,0,0,1,0-10.4341l1.334,1.49a5,5,0,0,0,0,7.4537Z"></path> <path d="M6.3994,28.8008a11.0019,11.0019,0,0,1,0-17.6006L7.6,12.8a9.0009,9.0009,0,0,0,0,14.4014Z"></path> <rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" class="cls-1" width="32" height="32"></rect> </g></svg>',
  2594. category: "SERVER",
  2595. type: "toggle",
  2596. onChange: () => {
  2597. this.kxsClient.isVoiceChatEnabled = !this.kxsClient.isVoiceChatEnabled;
  2598. this.kxsClient.updateLocalStorage();
  2599. this.kxsClient.voiceChat.toggleVoiceChat();
  2600. },
  2601. },
  2602. {
  2603. label: "Chat",
  2604. value: this.kxsClient.isKxsChatEnabled,
  2605. icon: '<svg fill="#000000" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <g> <path d="M232.727,238.545v186.182H281.6l-13.964,69.818l97.745-69.818H512V238.545H232.727z M477.091,389.818H365.382h-11.187 l-9.103,6.502l-25.912,18.508l5.003-25.01H281.6h-13.964V273.455h209.455V389.818z"></path> </g> </g> <g> <g> <path d="M279.273,17.455H0v186.182h65.164l97.745,69.818l-13.964-69.818h130.327V17.455z M244.364,168.727h-95.418h-42.582 l5.003,25.01L85.455,175.23l-9.104-6.502H65.164H34.909V52.364h209.455V168.727z"></path> </g> </g> <g> <g> <rect x="180.364" y="93.091" width="34.909" height="34.909"></rect> </g> </g> <g> <g> <rect x="122.182" y="93.091" width="34.909" height="34.909"></rect> </g> </g> <g> <g> <rect x="64" y="93.091" width="34.909" height="34.909"></rect> </g> </g> <g> <g> <rect x="413.091" y="314.182" width="34.909" height="34.909"></rect> </g> </g> <g> <g> <rect x="354.909" y="314.182" width="34.909" height="34.909"></rect> </g> </g> <g> <g> <rect x="296.727" y="314.182" width="34.909" height="34.909"></rect> </g> </g> </g></svg>',
  2606. category: "SERVER",
  2607. type: "toggle",
  2608. onChange: () => {
  2609. this.kxsClient.isKxsChatEnabled = !this.kxsClient.isKxsChatEnabled;
  2610. this.kxsClient.updateLocalStorage();
  2611. this.kxsClient.chat.toggleChat();
  2612. },
  2613. }
  2614. ],
  2615. });
  2616. this.addOption(MISC, {
  2617. label: "Game History",
  2618. value: true,
  2619. category: "MISC",
  2620. icon: '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M5.52786 16.7023C6.6602 18.2608 8.3169 19.3584 10.1936 19.7934C12.0703 20.2284 14.0409 19.9716 15.7434 19.0701C17.446 18.1687 18.766 16.6832 19.4611 14.8865C20.1562 13.0898 20.1796 11.1027 19.527 9.29011C18.8745 7.47756 17.5898 5.96135 15.909 5.02005C14.2282 4.07875 12.2641 3.77558 10.3777 4.16623C8.49129 4.55689 6.80919 5.61514 5.64045 7.14656C4.47171 8.67797 3.89482 10.5797 4.01579 12.5023M4.01579 12.5023L2.51579 11.0023M4.01579 12.5023L5.51579 11.0023" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M12 8V12L15 15" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>',
  2621. type: "click",
  2622. onChange: () => {
  2623. this.kxsClient.historyManager.show();
  2624. }
  2625. });
  2626. this.addOption(MECHANIC, {
  2627. label: "Win sound",
  2628. value: true,
  2629. type: "sub",
  2630. icon: '<svg fill="#000000" version="1.1" id="Trophy_x5F_cup" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M190.878,111.272c31.017-11.186,53.254-40.907,53.254-75.733l-0.19-8.509h-48.955V5H64.222v22.03H15.266l-0.19,8.509 c0,34.825,22.237,64.546,53.254,75.733c7.306,18.421,22.798,31.822,41.878,37.728v20c-0.859,15.668-14.112,29-30,29v18h-16v35H195 v-35h-16v-18c-15.888,0-29.141-13.332-30-29v-20C168.08,143.094,183.572,129.692,190.878,111.272z M195,44h30.563 c-0.06,0.427-0.103,1.017-0.171,1.441c-3.02,18.856-14.543,34.681-30.406,44.007C195.026,88.509,195,44,195,44z M33.816,45.441 c-0.068-0.424-0.111-1.014-0.171-1.441h30.563c0,0-0.026,44.509,0.013,45.448C48.359,80.122,36.837,64.297,33.816,45.441z M129.604,86.777l-20.255,13.52l6.599-23.442L96.831,61.77l24.334-0.967l8.44-22.844l8.44,22.844l24.334,0.967L143.26,76.856 l6.599,23.442L129.604,86.777z"></path> </g></svg>',
  2631. category: "MECHANIC",
  2632. fields: [
  2633. {
  2634. label: "Enable",
  2635. value: this.kxsClient.isWinSoundEnabled,
  2636. category: "MECHANIC",
  2637. icon: '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M3 11V13M6 10V14M9 11V13M12 9V15M15 6V18M18 10V14M21 11V13" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>',
  2638. type: "toggle",
  2639. onChange: () => {
  2640. this.kxsClient.isWinSoundEnabled = !this.kxsClient.isWinSoundEnabled;
  2641. this.kxsClient.updateLocalStorage();
  2642. },
  2643. },
  2644. {
  2645. label: "Sound URL",
  2646. value: this.kxsClient.soundLibrary.win_sound_url,
  2647. category: "MECHANIC",
  2648. icon: '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M3 11V13M6 10V14M9 11V13M12 9V15M15 6V18M18 10V14M21 11V13" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>',
  2649. type: "input",
  2650. placeholder: "URL of a sound",
  2651. onChange: (value) => {
  2652. this.kxsClient.soundLibrary.win_sound_url = value;
  2653. this.kxsClient.updateLocalStorage();
  2654. }
  2655. }
  2656. ]
  2657. });
  2658. this.addOption(MECHANIC, {
  2659. label: "Death sound",
  2660. value: true,
  2661. type: "sub",
  2662. icon: '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path fill-rule="evenodd" clip-rule="evenodd" d="M19 21C19 21.5523 18.5523 22 18 22H14H10H6C5.44771 22 5 21.5523 5 21V18.75C5 17.7835 4.2165 17 3.25 17C2.55964 17 2 16.4404 2 15.75V11C2 5.47715 6.47715 1 12 1C17.5228 1 22 5.47715 22 11V15.75C22 16.4404 21.4404 17 20.75 17C19.7835 17 19 17.7835 19 18.75V21ZM17 20V18.75C17 16.9358 18.2883 15.4225 20 15.075V11C20 6.58172 16.4183 3 12 3C7.58172 3 4 6.58172 4 11V15.075C5.71168 15.4225 7 16.9358 7 18.75V20H9V18C9 17.4477 9.44771 17 10 17C10.5523 17 11 17.4477 11 18V20H13V18C13 17.4477 13.4477 17 14 17C14.5523 17 15 17.4477 15 18V20H17ZM11 12.5C11 13.8807 8.63228 15 7.25248 15C5.98469 15 5.99206 14.055 6.00161 12.8306V12.8305C6.00245 12.7224 6.00331 12.6121 6.00331 12.5C6.00331 11.1193 7.12186 10 8.50166 10C9.88145 10 11 11.1193 11 12.5ZM17.9984 12.8306C17.9975 12.7224 17.9967 12.6121 17.9967 12.5C17.9967 11.1193 16.8781 10 15.4983 10C14.1185 10 13 11.1193 13 12.5C13 13.8807 15.3677 15 16.7475 15C18.0153 15 18.0079 14.055 17.9984 12.8306Z" fill="#000000"></path> </g></svg>',
  2663. category: "MECHANIC",
  2664. fields: [
  2665. {
  2666. label: "Enable",
  2667. value: this.kxsClient.isDeathSoundEnabled,
  2668. category: "MECHANIC",
  2669. icon: '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M3 11V13M6 10V14M9 11V13M12 9V15M15 6V18M18 10V14M21 11V13" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>',
  2670. type: "toggle",
  2671. onChange: () => {
  2672. this.kxsClient.isDeathSoundEnabled = !this.kxsClient.isDeathSoundEnabled;
  2673. this.kxsClient.updateLocalStorage();
  2674. },
  2675. },
  2676. {
  2677. label: "Sound URL",
  2678. value: this.kxsClient.soundLibrary.death_sound_url,
  2679. category: "MECHANIC",
  2680. icon: '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M3 11V13M6 10V14M9 11V13M12 9V15M15 12V18M15 6V8M18 10V14M21 11V13" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>',
  2681. type: "input",
  2682. placeholder: "URL of a sound",
  2683. onChange: (value) => {
  2684. this.kxsClient.soundLibrary.death_sound_url = value;
  2685. this.kxsClient.updateLocalStorage();
  2686. }
  2687. }
  2688. ]
  2689. });
  2690. this.addOption(HUD, {
  2691. label: "Clean Main Menu",
  2692. value: this.kxsClient.isMainMenuCleaned,
  2693. category: "HUD",
  2694. icon: '<svg fill="#000000" viewBox="0 0 32 32" id="icon" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <defs> <style> .cls-1 { fill: none; } </style> </defs> <title>clean</title> <rect x="20" y="18" width="6" height="2" transform="translate(46 38) rotate(-180)"></rect> <rect x="24" y="26" width="6" height="2" transform="translate(54 54) rotate(-180)"></rect> <rect x="22" y="22" width="6" height="2" transform="translate(50 46) rotate(-180)"></rect> <path d="M17.0029,20a4.8952,4.8952,0,0,0-2.4044-4.1729L22,3,20.2691,2,12.6933,15.126A5.6988,5.6988,0,0,0,7.45,16.6289C3.7064,20.24,3.9963,28.6821,4.01,29.04a1,1,0,0,0,1,.96H20.0012a1,1,0,0,0,.6-1.8C17.0615,25.5439,17.0029,20.0537,17.0029,20ZM11.93,16.9971A3.11,3.11,0,0,1,15.0041,20c0,.0381.0019.208.0168.4688L9.1215,17.8452A3.8,3.8,0,0,1,11.93,16.9971ZM15.4494,28A5.2,5.2,0,0,1,14,25H12a6.4993,6.4993,0,0,0,.9684,3H10.7451A16.6166,16.6166,0,0,1,10,24H8a17.3424,17.3424,0,0,0,.6652,4H6c.031-1.8364.29-5.8921,1.8027-8.5527l7.533,3.35A13.0253,13.0253,0,0,0,17.5968,28Z"></path> <rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" class="cls-1" width="32" height="32"></rect> </g></svg>',
  2695. type: "toggle",
  2696. onChange: (value) => {
  2697. this.kxsClient.isMainMenuCleaned = !this.kxsClient.isMainMenuCleaned;
  2698. this.kxsClient.MainMenuCleaning();
  2699. this.kxsClient.updateLocalStorage();
  2700. },
  2701. });
  2702. this.addOption(HUD, {
  2703. label: "Counters",
  2704. value: true,
  2705. category: "SERVER",
  2706. type: "sub",
  2707. icon: '<svg fill="#000000" viewBox="0 0 56 56" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M 13.1640 4.6562 L 43.3280 4.6562 C 43.1874 2.6875 42.0624 1.6328 39.9062 1.6328 L 16.5858 1.6328 C 14.4296 1.6328 13.3046 2.6875 13.1640 4.6562 Z M 8.1015 11.1484 L 47.9454 11.1484 C 47.5936 9.0156 46.5625 7.8438 44.2187 7.8438 L 11.8046 7.8438 C 9.4609 7.8438 8.4531 9.0156 8.1015 11.1484 Z M 10.2343 54.3672 L 45.7888 54.3672 C 50.6641 54.3672 53.1251 51.9297 53.1251 47.1016 L 53.1251 22.2109 C 53.1251 17.3828 50.6641 14.9453 45.7888 14.9453 L 10.2343 14.9453 C 5.3358 14.9453 2.8749 17.3594 2.8749 22.2109 L 2.8749 47.1016 C 2.8749 51.9297 5.3358 54.3672 10.2343 54.3672 Z"></path></g></svg>',
  2708. fields: [
  2709. {
  2710. label: "Show Kills",
  2711. value: this.kxsClient.isKillsVisible,
  2712. type: "toggle",
  2713. category: "HUD",
  2714. icon: '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M14.7245 11.2754L16 12.4999L10.0129 17.8218C8.05054 19.5661 5.60528 20.6743 3 20.9999L3.79443 19.5435C4.6198 18.0303 5.03249 17.2737 5.50651 16.5582C5.92771 15.9224 6.38492 15.3113 6.87592 14.7278C7.42848 14.071 8.0378 13.4615 9.25644 12.2426L12 9.49822M11.5 8.99787L17.4497 3.04989C18.0698 2.42996 19.0281 2.3017 19.7894 2.73674C20.9027 3.37291 21.1064 4.89355 20.1997 5.80024L19.8415 6.15847C19.6228 6.3771 19.3263 6.49992 19.0171 6.49992H18L16 8.49992V8.67444C16 9.16362 16 9.40821 15.9447 9.63839C15.8957 9.84246 15.8149 10.0375 15.7053 10.2165C15.5816 10.4183 15.4086 10.5913 15.0627 10.9372L14.2501 11.7498L11.5 8.99787Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>',
  2715. onChange: (value) => {
  2716. this.kxsClient.isKillsVisible = !this.kxsClient.isKillsVisible;
  2717. this.kxsClient.updateKillsVisibility();
  2718. this.kxsClient.updateLocalStorage();
  2719. },
  2720. },
  2721. {
  2722. label: "Show FPS",
  2723. value: this.kxsClient.isFpsVisible,
  2724. category: "HUD",
  2725. type: "toggle",
  2726. icon: '<svg fill="#000000" viewBox="0 0 24 24" id="60fps" data-name="Flat Line" xmlns="http://www.w3.org/2000/svg" class="icon flat-line"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><rect id="primary" x="10.5" y="8.5" width="14" height="7" rx="1" transform="translate(5.5 29.5) rotate(-90)" style="fill: none; stroke: #000000; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></rect><path id="primary-2" data-name="primary" d="M3,12H9a1,1,0,0,1,1,1v5a1,1,0,0,1-1,1H4a1,1,0,0,1-1-1V6A1,1,0,0,1,4,5h6" style="fill: none; stroke: #000000; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path></g></svg>',
  2727. onChange: (value) => {
  2728. this.kxsClient.isFpsVisible = !this.kxsClient.isFpsVisible;
  2729. this.kxsClient.updateFpsVisibility();
  2730. this.kxsClient.updateLocalStorage();
  2731. },
  2732. },
  2733. {
  2734. label: "Show Ping",
  2735. value: this.kxsClient.isPingVisible,
  2736. category: "HUD",
  2737. icon: '<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><defs><style>.a{fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;}</style></defs><path class="a" d="M34.6282,24.0793a14.7043,14.7043,0,0,0-22.673,1.7255"></path><path class="a" d="M43.5,20.5846a23.8078,23.8078,0,0,0-39,0"></path><path class="a" d="M43.5,20.5845,22.0169,29.0483a5.5583,5.5583,0,1,0,6.2116,8.7785l.0153.0206Z"></path></g></svg>',
  2738. type: "toggle",
  2739. onChange: (value) => {
  2740. this.kxsClient.isPingVisible = !this.kxsClient.isPingVisible;
  2741. this.kxsClient.updatePingVisibility();
  2742. this.kxsClient.updateLocalStorage();
  2743. },
  2744. }
  2745. ],
  2746. });
  2747. this.addOption(HUD, {
  2748. label: "Weapon Border",
  2749. value: this.kxsClient.isGunOverlayColored,
  2750. category: "HUD",
  2751. type: "toggle",
  2752. icon: '<svg fill="#000000" height="200px" width="200px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <g> <path d="M363.929,0l-12.346,12.346L340.036,0.799l-21.458,21.458l11.547,11.547L107.782,256.147L96.235,244.6l-21.458,21.458 l11.863,11.863c-17.171,21.661-18.478,51.842-3.925,74.805L399.683,35.755L363.929,0z"></path> </g> </g> <g> <g> <path d="M304.934,330.282c27.516-27.516,29.126-71.268,4.845-100.695l129.522-129.523l-30.506-30.506L115.402,362.954 l30.506,30.506l16.191-16.191L259.625,512l84.679-84.679l-57.279-79.13L304.934,330.282z M269.003,323.296l-18.666-25.788 l-3.561-4.919l5.696-5.696l15.814,15.814l21.458-21.458l-15.814-15.814l14.228-14.228c12.546,17.432,10.985,41.949-4.683,57.617 L269.003,323.296z"></path> </g> </g> </g></svg>',
  2753. onChange: (value) => {
  2754. this.kxsClient.isGunOverlayColored = !this.kxsClient.isGunOverlayColored;
  2755. this.kxsClient.updateLocalStorage();
  2756. this.kxsClient.hud.toggleWeaponBorderHandler();
  2757. },
  2758. });
  2759. this.addOption(HUD, {
  2760. label: "Chromatic Weapon Border",
  2761. value: this.kxsClient.isGunBorderChromatic,
  2762. category: "HUD",
  2763. type: "toggle",
  2764. icon: '<svg fill="#000000" height="200px" width="200px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512 512" xml:space="preserve"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <g> <path d="M256.005,13.477c-84.682,0-153.734,68.227-155.075,152.594c0.535-0.164,1.073-0.318,1.61-0.477 c0.341-0.101,0.681-0.204,1.022-0.303c1.366-0.398,2.733-0.781,4.107-1.146c0.166-0.043,0.331-0.084,0.497-0.127 c1.214-0.318,2.43-0.623,3.651-0.917c0.389-0.094,0.777-0.186,1.167-0.276c1.147-0.268,2.296-0.526,3.448-0.771 c0.253-0.055,0.506-0.112,0.76-0.166c1.377-0.288,2.757-0.557,4.139-0.813c0.347-0.064,0.695-0.124,1.044-0.186 c1.091-0.195,2.183-0.38,3.278-0.555c0.385-0.062,0.769-0.124,1.154-0.184c1.396-0.214,2.793-0.417,4.195-0.599 c0.112-0.014,0.225-0.026,0.337-0.041c1.298-0.167,2.599-0.316,3.902-0.454c0.404-0.042,0.808-0.084,1.213-0.124 c1.139-0.113,2.278-0.216,3.42-0.309c0.303-0.024,0.605-0.052,0.907-0.076c1.408-0.106,2.818-0.197,4.231-0.271 c0.333-0.018,0.668-0.03,1.001-0.045c1.128-0.054,2.258-0.097,3.389-0.13c0.402-0.012,0.803-0.024,1.205-0.033 c1.43-0.032,2.863-0.055,4.297-0.055c1.176,0,2.351,0.013,3.525,0.036c0.389,0.007,0.778,0.022,1.167,0.032 c0.787,0.02,1.574,0.041,2.361,0.072c0.46,0.018,0.921,0.042,1.382,0.064c0.715,0.033,1.429,0.067,2.144,0.108 c0.487,0.028,0.974,0.062,1.462,0.094c0.689,0.045,1.379,0.093,2.068,0.146c0.497,0.038,0.993,0.081,1.49,0.123 c0.68,0.059,1.36,0.119,2.039,0.186c0.497,0.047,0.993,0.098,1.49,0.149c0.684,0.072,1.368,0.148,2.051,0.228 c0.487,0.057,0.973,0.113,1.46,0.174c0.701,0.087,1.402,0.181,2.102,0.276c0.464,0.064,0.929,0.125,1.393,0.191 c0.74,0.106,1.479,0.221,2.218,0.336c0.423,0.066,0.846,0.128,1.269,0.198c0.85,0.138,1.698,0.287,2.546,0.437 c0.307,0.055,0.616,0.105,0.923,0.16c1.166,0.212,2.33,0.435,3.49,0.67c0.07,0.014,0.138,0.03,0.208,0.043 c1.082,0.22,2.161,0.449,3.239,0.688c0.352,0.078,0.704,0.163,1.055,0.242c0.793,0.181,1.588,0.362,2.379,0.554 c0.421,0.102,0.841,0.209,1.262,0.314c0.722,0.18,1.442,0.36,2.162,0.548c0.446,0.116,0.89,0.237,1.335,0.357 c0.693,0.187,1.387,0.377,2.078,0.57c0.453,0.127,0.906,0.258,1.359,0.39c0.683,0.198,1.367,0.4,2.048,0.606 c0.451,0.136,0.902,0.273,1.353,0.414c0.685,0.212,1.369,0.43,2.051,0.651c0.439,0.141,0.878,0.283,1.316,0.428 c0.703,0.232,1.402,0.471,2.101,0.712c0.414,0.142,0.828,0.283,1.24,0.427c0.747,0.262,1.491,0.534,2.235,0.805 c0.36,0.132,0.722,0.26,1.081,0.395c0.887,0.331,1.771,0.671,2.654,1.015c0.212,0.083,0.425,0.161,0.636,0.245 c1.108,0.437,2.212,0.884,3.313,1.342c0.125,0.052,0.249,0.107,0.374,0.16c0.958,0.401,1.913,0.81,2.865,1.226 c0.329,0.144,0.656,0.295,0.985,0.441c0.744,0.332,1.487,0.665,2.227,1.006c0.391,0.181,0.779,0.365,1.169,0.548 c0.673,0.316,1.345,0.634,2.015,0.959c0.417,0.202,0.832,0.408,1.248,0.613c0.64,0.316,1.278,0.634,1.914,0.957 c0.425,0.216,0.849,0.435,1.273,0.654c0.625,0.323,1.248,0.65,1.868,0.981c0.424,0.226,0.847,0.452,1.269,0.68 c0.621,0.336,1.239,0.678,1.856,1.021c0.413,0.23,0.826,0.459,1.237,0.692c0.63,0.357,1.257,0.721,1.882,1.086 c0.392,0.228,0.784,0.454,1.174,0.685c0.664,0.394,1.323,0.795,1.982,1.197c0.344,0.21,0.69,0.417,1.034,0.629 c0.803,0.498,1.601,1.003,2.396,1.513c0.118,0.076,0.237,0.148,0.355,0.224l0.297-0.218l0.297,0.218 c0.118-0.076,0.237-0.148,0.355-0.224c0.795-0.51,1.593-1.015,2.396-1.513c0.343-0.212,0.689-0.419,1.034-0.629 c0.659-0.402,1.318-0.803,1.982-1.197c0.39-0.231,0.782-0.457,1.174-0.685c0.626-0.365,1.253-0.729,1.882-1.086 c0.411-0.233,0.824-0.462,1.236-0.692c0.617-0.343,1.235-0.685,1.856-1.021c0.422-0.229,0.845-0.455,1.268-0.68 c0.622-0.331,1.246-0.658,1.871-0.982c0.422-0.218,0.845-0.436,1.269-0.652c0.637-0.323,1.276-0.642,1.917-0.959 c0.415-0.205,0.83-0.41,1.247-0.612c0.669-0.324,1.341-0.642,2.015-0.959c0.39-0.183,0.778-0.368,1.169-0.548 c0.74-0.341,1.483-0.674,2.227-1.006c0.328-0.146,0.655-0.296,0.985-0.441c0.951-0.418,1.907-0.826,2.865-1.226 c0.125-0.052,0.249-0.108,0.374-0.16c1.1-0.458,2.203-0.905,3.313-1.342c0.211-0.084,0.425-0.162,0.636-0.245 c0.882-0.344,1.766-0.685,2.654-1.015c0.359-0.134,0.721-0.262,1.081-0.395c0.744-0.273,1.488-0.543,2.235-0.805 c0.413-0.145,0.827-0.286,1.24-0.427c0.699-0.24,1.399-0.479,2.102-0.712c0.438-0.145,0.877-0.287,1.316-0.428 c0.683-0.221,1.367-0.438,2.051-0.651c0.45-0.139,0.901-0.277,1.353-0.414c0.681-0.206,1.364-0.407,2.048-0.606 c0.452-0.131,0.905-0.261,1.359-0.39c0.691-0.195,1.385-0.384,2.078-0.57c0.445-0.12,0.89-0.24,1.335-0.357 c0.72-0.189,1.44-0.369,2.162-0.548c0.421-0.105,0.841-0.212,1.262-0.314c0.791-0.191,1.585-0.373,2.379-0.554 c0.352-0.081,0.703-0.164,1.055-0.242c1.078-0.239,2.157-0.468,3.239-0.688c0.07-0.014,0.138-0.029,0.208-0.043 c1.162-0.234,2.325-0.457,3.49-0.67c0.307-0.057,0.615-0.106,0.922-0.161c0.848-0.149,1.697-0.298,2.548-0.437 c0.422-0.069,0.844-0.131,1.266-0.197c0.739-0.115,1.479-0.23,2.219-0.336c0.463-0.067,0.928-0.128,1.392-0.191 c0.701-0.095,1.401-0.189,2.103-0.276c0.487-0.061,0.973-0.117,1.461-0.174c0.682-0.08,1.366-0.155,2.05-0.228 c0.497-0.051,0.993-0.102,1.49-0.149c0.679-0.066,1.359-0.127,2.039-0.186c0.497-0.042,0.993-0.085,1.49-0.123 c0.688-0.054,1.378-0.101,2.068-0.146c0.487-0.032,0.974-0.066,1.462-0.094c0.715-0.042,1.429-0.076,2.144-0.108 c0.46-0.021,0.921-0.045,1.382-0.064c0.786-0.03,1.574-0.051,2.361-0.072c0.389-0.01,0.778-0.024,1.167-0.032 c1.175-0.023,2.35-0.036,3.525-0.036c1.435,0,2.867,0.022,4.297,0.055c0.402,0.009,0.803,0.021,1.205,0.033 c1.132,0.033,2.261,0.077,3.388,0.13c0.334,0.016,0.668,0.028,1.002,0.045c1.413,0.075,2.823,0.166,4.23,0.272 c0.303,0.023,0.605,0.051,0.907,0.076c1.142,0.093,2.282,0.195,3.42,0.309c0.405,0.04,0.808,0.081,1.213,0.124 c1.303,0.138,2.604,0.288,3.902,0.454c0.112,0.015,0.225,0.026,0.337,0.041c1.401,0.182,2.799,0.385,4.194,0.599 c0.386,0.06,0.77,0.122,1.156,0.184c1.094,0.175,2.186,0.36,3.277,0.555c0.348,0.063,0.696,0.122,1.044,0.186 c1.383,0.255,2.763,0.526,4.139,0.813c0.253,0.054,0.507,0.111,0.76,0.166c1.152,0.245,2.3,0.503,3.448,0.771 c0.39,0.091,0.778,0.183,1.167,0.276c1.218,0.294,2.435,0.598,3.648,0.915c0.167,0.043,0.334,0.084,0.501,0.128 c1.373,0.364,2.74,0.749,4.106,1.145c0.341,0.1,0.681,0.203,1.022,0.304c0.537,0.159,1.074,0.314,1.61,0.477 C409.734,81.704,340.686,13.477,256.005,13.477z"></path> </g> </g> <g> <g> <path d="M436.614,210.342c-0.136,0.588-0.291,1.172-0.433,1.758c-0.163,0.671-0.325,1.341-0.495,2.01 c-0.317,1.249-0.651,2.49-0.993,3.73c-0.161,0.585-0.317,1.174-0.484,1.757c-0.482,1.682-0.986,3.354-1.515,5.017 c-0.039,0.124-0.075,0.25-0.114,0.374c-0.571,1.784-1.175,3.555-1.8,5.318c-0.197,0.554-0.406,1.103-0.608,1.655 c-0.442,1.21-0.891,2.417-1.358,3.617c-0.253,0.652-0.515,1.3-0.775,1.948c-0.445,1.108-0.9,2.211-1.367,3.309 c-0.277,0.652-0.555,1.304-0.84,1.953c-0.497,1.133-1.008,2.258-1.527,3.379c-0.268,0.579-0.53,1.161-0.803,1.738 c-0.687,1.446-1.395,2.883-2.119,4.31c-0.118,0.233-0.229,0.469-0.348,0.701c-0.843,1.644-1.713,3.273-2.603,4.89 c-0.286,0.52-0.584,1.032-0.875,1.548c-0.624,1.107-1.254,2.211-1.9,3.306c-0.358,0.607-0.724,1.209-1.089,1.811 c-0.61,1.007-1.228,2.008-1.857,3.003c-0.383,0.607-0.767,1.211-1.158,1.813c-0.659,1.016-1.331,2.023-2.01,3.027 c-0.368,0.544-0.731,1.091-1.104,1.63c-0.867,1.254-1.753,2.495-2.652,3.727c-0.196,0.268-0.384,0.542-0.579,0.808 c-1.093,1.483-2.21,2.946-3.346,4.397c-0.357,0.455-0.725,0.902-1.086,1.354c-0.801,1.003-1.608,2.001-2.43,2.987 c-0.449,0.54-0.906,1.073-1.362,1.608c-0.763,0.895-1.534,1.785-2.314,2.666c-0.477,0.539-0.956,1.076-1.439,1.609 c-0.811,0.894-1.633,1.778-2.462,2.658c-0.455,0.482-0.905,0.968-1.367,1.446c-1.03,1.07-2.075,2.123-3.131,3.168 c-0.268,0.265-0.529,0.537-0.798,0.8c-1.322,1.293-2.666,2.565-4.027,3.819c-0.409,0.377-0.827,0.742-1.24,1.115 c-0.973,0.881-1.952,1.756-2.945,2.617c-0.526,0.456-1.059,0.905-1.591,1.356c-0.909,0.771-1.825,1.534-2.75,2.288 c-0.557,0.454-1.117,0.906-1.681,1.355c-0.951,0.757-1.912,1.502-2.879,2.241c-0.532,0.406-1.06,0.817-1.596,1.217 c-1.172,0.876-2.359,1.734-3.554,2.584c-0.337,0.239-0.667,0.487-1.005,0.724c-1.528,1.071-3.076,2.119-4.638,3.145 c-0.451,0.296-0.911,0.581-1.367,0.874c-1.131,0.729-2.268,1.45-3.417,2.156c-0.598,0.366-1.202,0.725-1.804,1.085 c-1.034,0.618-2.073,1.228-3.121,1.828c-0.635,0.363-1.272,0.724-1.913,1.08c-1.067,0.592-2.142,1.173-3.222,1.746 c-0.61,0.323-1.217,0.651-1.832,0.968c-0.13,0.067-0.258,0.138-0.389,0.205l0.04,0.361l-0.333,0.146 c0.008,0.161,0.01,0.322,0.018,0.483c0.03,0.645,0.049,1.289,0.073,1.935c0.046,1.27,0.083,2.539,0.103,3.808 c0.011,0.696,0.017,1.392,0.02,2.088c0.005,1.247-0.004,2.491-0.023,3.736c-0.01,0.67-0.018,1.34-0.036,2.01 c-0.037,1.388-0.095,2.772-0.164,4.156c-0.025,0.505-0.04,1.009-0.069,1.514c-0.108,1.877-0.242,3.752-0.407,5.621 c-0.033,0.382-0.079,0.761-0.115,1.143c-0.14,1.488-0.294,2.973-0.469,4.454c-0.078,0.656-0.168,1.31-0.252,1.965 c-0.157,1.214-0.323,2.427-0.505,3.638c-0.106,0.708-0.217,1.414-0.331,2.121c-0.191,1.18-0.395,2.356-0.608,3.531 c-0.124,0.685-0.247,1.372-0.379,2.056c-0.247,1.283-0.515,2.56-0.788,3.836c-0.119,0.553-0.229,1.109-0.353,1.661 c-0.405,1.801-0.833,3.595-1.29,5.382c-0.099,0.388-0.21,0.771-0.312,1.157c-0.371,1.412-0.754,2.821-1.158,4.224 c-0.189,0.654-0.389,1.304-0.584,1.956c-0.342,1.139-0.69,2.275-1.054,3.407c-0.225,0.699-0.455,1.397-0.687,2.093 c-0.366,1.097-0.745,2.19-1.132,3.28c-0.242,0.681-0.482,1.363-0.732,2.04c-0.431,1.172-0.88,2.337-1.334,3.498 c-0.223,0.571-0.438,1.145-0.667,1.713c-0.682,1.696-1.387,3.383-2.119,5.058c-0.155,0.356-0.323,0.707-0.48,1.062 c-0.596,1.339-1.201,2.673-1.828,3.998c-0.295,0.622-0.601,1.238-0.901,1.857c-0.515,1.059-1.036,2.114-1.571,3.162 c-0.338,0.662-0.681,1.321-1.028,1.979c-0.532,1.012-1.074,2.02-1.625,3.023c-0.353,0.643-0.706,1.286-1.066,1.925 c-0.605,1.071-1.225,2.132-1.851,3.192c-0.321,0.543-0.635,1.091-0.962,1.631c-0.948,1.567-1.915,3.122-2.908,4.661 c-0.177,0.274-0.363,0.542-0.542,0.815c-0.839,1.284-1.691,2.561-2.562,3.824c-0.38,0.55-0.77,1.093-1.156,1.64 c-0.694,0.985-1.395,1.965-2.109,2.936c-0.432,0.587-0.87,1.171-1.309,1.753c-0.703,0.934-1.417,1.861-2.139,2.782 c-0.443,0.566-0.886,1.131-1.336,1.693c-0.787,0.981-1.59,1.951-2.398,2.917c-0.395,0.472-0.783,0.949-1.184,1.417 c-1.207,1.413-2.433,2.813-3.684,4.192c-0.119,0.131-0.243,0.257-0.362,0.389c-1.146,1.255-2.309,2.494-3.491,3.718 c-0.434,0.45-0.879,0.891-1.318,1.337c-0.889,0.903-1.785,1.8-2.694,2.686c-0.503,0.49-1.01,0.974-1.519,1.458 c-0.433,0.412-0.856,0.833-1.293,1.24c23.044,12.82,49.005,19.586,75.238,19.589c0.002,0,0.006,0,0.008,0 c26.778,0,53.256-6.96,76.576-20.133c24-13.557,44.018-33.419,57.887-57.44C533.598,347.601,509.03,253.68,436.614,210.342z"></path> </g> </g> <g> <g> <path d="M357.097,188.296c-26.339,0-52.539,6.858-75.519,19.563c0.206,0.192,0.401,0.394,0.605,0.585 c1.334,1.256,2.652,2.526,3.946,3.82c0.146,0.146,0.298,0.289,0.443,0.435c1.417,1.426,2.803,2.881,4.172,4.352 c0.372,0.4,0.74,0.804,1.108,1.208c1.12,1.225,2.224,2.466,3.311,3.723c0.272,0.314,0.548,0.623,0.818,0.939 c1.302,1.527,2.579,3.077,3.831,4.647c0.299,0.374,0.589,0.754,0.885,1.13c1.013,1.291,2.011,2.596,2.991,3.915 c0.303,0.408,0.61,0.814,0.909,1.224c1.192,1.632,2.364,3.283,3.505,4.959c0.19,0.278,0.372,0.562,0.56,0.842 c0.973,1.444,1.926,2.906,2.861,4.382c0.296,0.466,0.59,0.933,0.882,1.402c1.085,1.746,2.154,3.505,3.187,5.294 c0.721,1.249,1.421,2.506,2.112,3.767c0.195,0.355,0.387,0.712,0.578,1.068c0.539,0.999,1.068,2.003,1.588,3.009 c0.154,0.3,0.313,0.599,0.465,0.899c0.645,1.265,1.275,2.536,1.888,3.812c0.133,0.278,0.262,0.556,0.394,0.835 c0.493,1.036,0.974,2.076,1.446,3.12c0.169,0.372,0.337,0.745,0.503,1.118c0.536,1.205,1.061,2.413,1.57,3.627 c0.042,0.101,0.087,0.201,0.129,0.302c0.547,1.31,1.073,2.627,1.589,3.949c0.141,0.36,0.279,0.723,0.417,1.085 c0.399,1.042,0.788,2.087,1.168,3.135c0.12,0.331,0.242,0.661,0.36,0.993c0.472,1.328,0.932,2.663,1.374,4.001 c0.082,0.246,0.157,0.495,0.238,0.741c0.364,1.119,0.717,2.243,1.06,3.369c0.118,0.389,0.235,0.777,0.351,1.166 c0.354,1.194,0.697,2.391,1.028,3.592c0.048,0.175,0.1,0.349,0.147,0.525c0.37,1.364,0.721,2.732,1.06,4.104 c0.091,0.367,0.178,0.736,0.265,1.104c0.127,0.529,0.259,1.056,0.382,1.587c37.925-22.771,64.617-60.932,72.753-104.53 C391.963,191.252,374.736,188.296,357.097,188.296z"></path> </g> </g> <g> <g> <path d="M229.161,477.688c-0.508-0.483-1.014-0.967-1.516-1.455c-0.909-0.886-1.806-1.785-2.696-2.688 c-0.439-0.446-0.883-0.886-1.317-1.336c-1.182-1.224-2.346-2.464-3.491-3.718c-0.119-0.13-0.243-0.257-0.362-0.389 c-1.252-1.379-2.477-2.779-3.684-4.192c-0.4-0.468-0.788-0.946-1.184-1.417c-0.808-0.966-1.611-1.936-2.398-2.917 c-0.45-0.561-0.893-1.126-1.336-1.693c-0.722-0.922-1.435-1.848-2.139-2.782c-0.439-0.582-0.877-1.166-1.309-1.753 c-0.714-0.971-1.414-1.952-2.109-2.936c-0.386-0.547-0.776-1.089-1.156-1.64c-0.871-1.264-1.723-2.54-2.562-3.824 c-0.179-0.273-0.364-0.541-0.542-0.815c-0.994-1.539-1.962-3.096-2.91-4.662c-0.326-0.538-0.638-1.084-0.958-1.625 c-0.627-1.061-1.249-2.124-1.854-3.197c-0.36-0.639-0.713-1.282-1.066-1.924c-0.551-1.003-1.093-2.011-1.625-3.023 c-0.346-0.658-0.689-1.317-1.028-1.979c-0.535-1.049-1.056-2.104-1.571-3.162c-0.301-0.619-0.608-1.235-0.901-1.857 c-0.627-1.325-1.232-2.659-1.828-3.998c-0.157-0.355-0.325-0.706-0.48-1.062c-0.732-1.674-1.435-3.362-2.119-5.058 c-0.229-0.568-0.443-1.142-0.667-1.713c-0.454-1.162-0.903-2.327-1.334-3.498c-0.249-0.678-0.491-1.36-0.732-2.04 c-0.387-1.09-0.765-2.183-1.132-3.28c-0.232-0.696-0.463-1.394-0.687-2.093c-0.363-1.133-0.713-2.271-1.055-3.412 c-0.195-0.649-0.395-1.297-0.582-1.949c-0.405-1.404-0.788-2.814-1.16-4.228c-0.101-0.386-0.212-0.768-0.311-1.155 c-0.457-1.786-0.885-3.58-1.29-5.382c-0.124-0.552-0.234-1.108-0.353-1.661c-0.275-1.276-0.541-2.553-0.788-3.836 c-0.132-0.684-0.254-1.37-0.378-2.056c-0.213-1.175-0.417-2.351-0.608-3.531c-0.114-0.706-0.225-1.412-0.331-2.121 c-0.182-1.21-0.347-2.423-0.505-3.638c-0.085-0.655-0.175-1.308-0.252-1.965c-0.176-1.481-0.329-2.966-0.469-4.454 c-0.036-0.382-0.082-0.761-0.115-1.143c-0.164-1.869-0.299-3.744-0.407-5.621c-0.029-0.504-0.044-1.008-0.069-1.512 c-0.069-1.385-0.126-2.771-0.164-4.16c-0.018-0.667-0.025-1.335-0.036-2.004c-0.019-1.246-0.028-2.492-0.023-3.741 c0.003-0.695,0.009-1.391,0.02-2.087c0.02-1.269,0.057-2.539,0.103-3.808c0.023-0.645,0.042-1.289,0.073-1.935 c0.007-0.162,0.01-0.322,0.018-0.484l-0.333-0.146l0.04-0.361c-0.13-0.067-0.258-0.138-0.389-0.205 c-0.615-0.317-1.222-0.645-1.833-0.968c-1.081-0.573-2.157-1.154-3.224-1.747c-0.638-0.355-1.274-0.714-1.907-1.076 c-1.051-0.601-2.092-1.213-3.129-1.833c-0.601-0.358-1.202-0.716-1.798-1.081c-1.15-0.706-2.286-1.427-3.418-2.157 c-0.454-0.293-0.915-0.577-1.367-0.874c-1.563-1.025-3.109-2.072-4.636-3.143c-0.343-0.24-0.678-0.492-1.02-0.735 c-1.189-0.845-2.37-1.7-3.537-2.571c-0.54-0.403-1.071-0.816-1.606-1.224c-0.964-0.737-1.922-1.479-2.871-2.234 c-0.565-0.45-1.126-0.904-1.687-1.361c-0.921-0.751-1.833-1.511-2.738-2.278c-0.536-0.454-1.072-0.906-1.602-1.366 c-0.984-0.854-1.955-1.722-2.921-2.595c-0.421-0.38-0.849-0.755-1.266-1.14c-1.358-1.251-2.698-2.519-4.017-3.809 c-0.281-0.275-0.552-0.558-0.832-0.835c-1.044-1.034-2.078-2.075-3.097-3.133c-0.466-0.484-0.924-0.978-1.385-1.468 c-0.821-0.871-1.637-1.747-2.441-2.634c-0.489-0.539-0.973-1.082-1.456-1.626c-0.774-0.875-1.54-1.757-2.297-2.647 c-0.461-0.541-0.923-1.08-1.377-1.625c-0.814-0.977-1.612-1.965-2.405-2.958c-0.368-0.461-0.744-0.917-1.108-1.382 c-1.133-1.446-2.248-2.906-3.337-4.385c-0.21-0.284-0.41-0.574-0.617-0.86c-0.885-1.215-1.758-2.439-2.614-3.674 c-0.379-0.548-0.747-1.103-1.12-1.654c-0.672-0.995-1.339-1.993-1.992-3.001c-0.395-0.609-0.783-1.22-1.171-1.834 c-0.624-0.986-1.236-1.978-1.841-2.978c-0.369-0.609-0.739-1.216-1.1-1.83c-0.642-1.088-1.269-2.186-1.888-3.286 c-0.295-0.523-0.596-1.04-0.884-1.565c-0.889-1.615-1.758-3.241-2.6-4.883c-0.124-0.241-0.239-0.487-0.361-0.729 c-0.719-1.417-1.421-2.842-2.103-4.279c-0.278-0.584-0.543-1.174-0.815-1.76c-0.516-1.115-1.024-2.234-1.518-3.36 c-0.286-0.651-0.564-1.304-0.843-1.959c-0.466-1.098-0.922-2.201-1.367-3.309c-0.26-0.647-0.521-1.294-0.773-1.944 c-0.467-1.201-0.917-2.409-1.36-3.622c-0.201-0.551-0.41-1.099-0.606-1.652c-0.625-1.763-1.228-3.535-1.8-5.319 c-0.039-0.123-0.074-0.247-0.113-0.369c-0.53-1.666-1.035-3.343-1.518-5.027c-0.166-0.578-0.32-1.162-0.48-1.742 c-0.344-1.246-0.679-2.493-0.998-3.748c-0.169-0.662-0.329-1.326-0.491-1.991c-0.143-0.59-0.299-1.178-0.436-1.77 C2.971,253.679-21.598,347.601,20.747,420.946c13.866,24.018,33.884,43.88,57.888,57.436 c23.322,13.172,49.804,20.136,76.584,20.137c0.002,0,0.005,0,0.007,0c26.227,0,52.183-6.767,75.23-19.589 C230.018,478.523,229.594,478.101,229.161,477.688z"></path> </g> </g> <g> <g> <path d="M154.899,188.295c-17.638,0-34.866,2.956-51.358,8.799c8.136,43.599,34.828,81.76,72.749,104.53 c0.126-0.53,0.258-1.058,0.385-1.586c0.089-0.368,0.175-0.737,0.266-1.104c0.338-1.372,0.689-2.74,1.06-4.104 c0.047-0.176,0.099-0.349,0.147-0.525c0.33-1.201,0.673-2.398,1.028-3.592c0.115-0.39,0.233-0.778,0.351-1.166 c0.342-1.126,0.695-2.25,1.06-3.369c0.081-0.247,0.157-0.495,0.238-0.741c0.442-1.338,0.9-2.672,1.374-4.001 c0.118-0.332,0.24-0.662,0.36-0.993c0.38-1.049,0.769-2.093,1.168-3.135c0.138-0.361,0.276-0.724,0.417-1.085 c0.516-1.321,1.042-2.637,1.589-3.949c0.042-0.101,0.087-0.201,0.129-0.302c0.509-1.214,1.034-2.421,1.57-3.627 c0.166-0.373,0.334-0.746,0.503-1.118c0.472-1.044,0.955-2.083,1.446-3.12c0.132-0.278,0.26-0.557,0.394-0.835 c0.614-1.277,1.243-2.547,1.888-3.812c0.152-0.301,0.311-0.6,0.465-0.899c0.52-1.006,1.049-2.01,1.588-3.009 c0.192-0.356,0.384-0.713,0.578-1.068c0.69-1.262,1.391-2.518,2.112-3.767c1.033-1.789,2.102-3.548,3.187-5.294 c0.292-0.469,0.586-0.936,0.882-1.402c0.936-1.476,1.888-2.937,2.861-4.382c0.188-0.28,0.37-0.564,0.56-0.842 c1.142-1.676,2.312-3.327,3.505-4.959c0.3-0.411,0.606-0.817,0.909-1.224c0.98-1.32,1.977-2.625,2.991-3.915 c0.296-0.376,0.587-0.756,0.885-1.13c1.254-1.571,2.529-3.12,3.831-4.647c0.27-0.316,0.546-0.625,0.818-0.939 c1.087-1.257,2.19-2.497,3.311-3.722c0.368-0.403,0.736-0.807,1.108-1.208c1.369-1.472,2.755-2.926,4.172-4.352 c0.146-0.146,0.297-0.289,0.443-0.435c1.294-1.294,2.612-2.565,3.945-3.82c0.205-0.192,0.4-0.394,0.605-0.585 C207.437,195.152,181.237,188.295,154.899,188.295z"></path> </g> </g> <g> <g> <path d="M308.604,346.356c-0.375,0.111-0.751,0.224-1.126,0.333c-1.352,0.392-2.707,0.77-4.067,1.129 c-0.183,0.048-0.365,0.092-0.548,0.14c-1.2,0.314-2.404,0.614-3.61,0.902c-0.394,0.095-0.788,0.188-1.183,0.279 c-1.15,0.268-2.302,0.524-3.458,0.769c-0.249,0.053-0.499,0.11-0.749,0.161c-1.385,0.288-2.774,0.558-4.166,0.814 c-0.336,0.063-0.674,0.119-1.011,0.18c-1.108,0.197-2.219,0.385-3.33,0.561c-0.376,0.06-0.752,0.121-1.128,0.178 c-1.406,0.215-2.815,0.418-4.227,0.601c-0.096,0.012-0.193,0.022-0.29,0.035c-1.319,0.169-2.642,0.319-3.967,0.458 c-0.398,0.042-0.796,0.082-1.195,0.121c-1.152,0.114-2.305,0.217-3.461,0.31c-0.298,0.024-0.594,0.051-0.891,0.074 c-1.416,0.106-2.834,0.197-4.255,0.272c-0.334,0.018-0.669,0.03-1.004,0.045c-1.131,0.054-2.266,0.097-3.4,0.13 c-0.407,0.012-0.813,0.023-1.219,0.033c-1.436,0.032-2.875,0.055-4.316,0.055c-1.441,0-2.88-0.022-4.316-0.055 c-0.407-0.009-0.814-0.021-1.219-0.033c-1.135-0.033-2.269-0.077-3.401-0.13c-0.335-0.016-0.67-0.028-1.004-0.045 c-1.421-0.075-2.84-0.165-4.255-0.272c-0.298-0.022-0.595-0.049-0.891-0.074c-1.156-0.093-2.309-0.196-3.461-0.31 c-0.399-0.039-0.796-0.079-1.194-0.121c-1.325-0.139-2.649-0.291-3.968-0.458c-0.096-0.012-0.193-0.022-0.29-0.035 c-1.413-0.183-2.821-0.386-4.227-0.601c-0.376-0.058-0.752-0.118-1.128-0.178c-1.112-0.177-2.223-0.364-3.33-0.561 c-0.337-0.061-0.674-0.117-1.011-0.18c-1.392-0.255-2.781-0.526-4.166-0.814c-0.25-0.052-0.499-0.108-0.749-0.161 c-1.156-0.245-2.307-0.502-3.458-0.769c-0.395-0.092-0.788-0.185-1.183-0.279c-1.206-0.289-2.41-0.588-3.61-0.902 c-0.183-0.047-0.365-0.092-0.548-0.14c-1.36-0.359-2.715-0.738-4.067-1.129c-0.376-0.109-0.751-0.222-1.126-0.333 c-0.513-0.151-1.028-0.298-1.538-0.455c0.756,44.222,20.455,86.416,54.141,115.261c33.685-28.845,53.385-71.039,54.143-115.261 C309.631,346.057,309.117,346.204,308.604,346.356z"></path> </g> </g> <g> <g> <path d="M289.37,265.857c-8.877-15.374-20.073-28.874-33.371-40.251c-13.298,11.376-24.494,24.875-33.371,40.251 c-8.876,15.374-14.969,31.819-18.173,49.024c16.502,5.827,33.79,8.773,51.544,8.773c17.754,0,35.045-2.945,51.546-8.773 C304.339,297.676,298.247,281.232,289.37,265.857z"></path> </g> </g> </g></svg>',
  2765. onChange: (value) => {
  2766. this.kxsClient.isGunBorderChromatic = !this.kxsClient.isGunBorderChromatic;
  2767. this.kxsClient.updateLocalStorage();
  2768. this.kxsClient.hud.toggleChromaticWeaponBorder();
  2769. },
  2770. });
  2771. this.addOption(HUD, {
  2772. label: "Focus Mode",
  2773. value: "Hold Left CTRL for 1 seconds to toggle Focus Mode.\nWhen enabled, the HUD will dim and notifications will appear.",
  2774. category: "HUD",
  2775. type: "info",
  2776. icon: '<svg version="1.1" id="Uploaded to svgrepo.com" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32" xml:space="preserve" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M30.146,28.561l-1.586,1.586c-0.292,0.292-0.676,0.438-1.061,0.438s-0.768-0.146-1.061-0.438 l-4.293-4.293l-2.232,2.232c-0.391,0.391-0.902,0.586-1.414,0.586s-1.024-0.195-1.414-0.586l-0.172-0.172 c-0.781-0.781-0.781-2.047,0-2.828l8.172-8.172c0.391-0.391,0.902-0.586,1.414-0.586s1.024,0.195,1.414,0.586l0.172,0.172 c0.781,0.781,0.781,2.047,0,2.828l-2.232,2.232l4.293,4.293C30.731,27.024,30.731,27.976,30.146,28.561z M22.341,18.244 l-4.097,4.097L3.479,13.656C2.567,13.12,2,12.128,2,11.07V3c0-0.551,0.449-1,1-1h8.07c1.058,0,2.049,0.567,2.586,1.479 L22.341,18.244z M19.354,19.354c0.195-0.195,0.195-0.512,0-0.707l-15.5-15.5c-0.195-0.195-0.512-0.195-0.707,0s-0.195,0.512,0,0.707 l15.5,15.5C18.744,19.451,18.872,19.5,19,19.5S19.256,19.451,19.354,19.354z" fill="#000000"></path> </g></svg>',
  2777. onChange: () => {
  2778. }
  2779. });
  2780. this.addOption(HUD, {
  2781. label: "Health Bar Indicator",
  2782. value: this.kxsClient.isHealBarIndicatorEnabled,
  2783. type: "toggle",
  2784. icon: '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path fill-rule="evenodd" clip-rule="evenodd" d="M12.0001 8.59997C13.3334 7.01474 15 5.42847 17 5.42847C19.6667 5.42847 22 7.52847 22 11.2855C22 13.7143 20.2683 16.4912 18.1789 18.9912C16.5956 20.8955 14.7402 22.5713 13.2302 22.5713H10.7698C9.25981 22.5713 7.40446 20.8955 5.82112 18.9912C3.73174 16.4912 2 13.7143 2 11.2855C2 7.52847 4.33333 5.42847 7 5.42847C9 5.42847 10.6667 7.01474 12.0001 8.59997Z" fill="#000000"></path> </g></svg>',
  2785. category: "HUD",
  2786. onChange: () => {
  2787. this.kxsClient.isHealBarIndicatorEnabled = !this.kxsClient.isHealBarIndicatorEnabled;
  2788. this.kxsClient.updateLocalStorage();
  2789. },
  2790. });
  2791. this.addOption(HUD, {
  2792. label: "Message Open/Close RSHIFT Menu",
  2793. value: this.kxsClient.isNotifyingForToggleMenu,
  2794. type: "toggle",
  2795. icon: '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title></title> <g id="Complete"> <g id="info-circle"> <g> <circle cx="12" cy="12" data-name="--Circle" fill="none" id="_--Circle" r="10" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></circle> <line fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="12" x2="12" y1="12" y2="16"></line> <line fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" x1="12" x2="12" y1="8" y2="8"></line> </g> </g> </g> </g></svg>',
  2796. category: "HUD",
  2797. onChange: (value) => {
  2798. this.kxsClient.isNotifyingForToggleMenu = !this.kxsClient.isNotifyingForToggleMenu;
  2799. this.kxsClient.updateLocalStorage();
  2800. },
  2801. });
  2802. this.addOption(SERVER, {
  2803. label: "Webhook URL",
  2804. value: this.kxsClient.discordWebhookUrl || "",
  2805. icon: '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path fill-rule="evenodd" clip-rule="evenodd" d="M12.52 3.046a3 3 0 0 0-2.13 5.486 1 1 0 0 1 .306 1.38l-3.922 6.163a2 2 0 1 1-1.688-1.073l3.44-5.405a5 5 0 1 1 8.398-2.728 1 1 0 1 1-1.97-.348 3 3 0 0 0-2.433-3.475zM10 6a2 2 0 1 1 3.774.925l3.44 5.405a5 5 0 1 1-1.427 8.5 1 1 0 0 1 1.285-1.532 3 3 0 1 0 .317-4.83 1 1 0 0 1-1.38-.307l-3.923-6.163A2 2 0 0 1 10 6zm-5.428 6.9a1 1 0 0 1-.598 1.281A3 3 0 1 0 8.001 17a1 1 0 0 1 1-1h8.266a2 2 0 1 1 0 2H9.9a5 5 0 1 1-6.61-5.698 1 1 0 0 1 1.282.597Z" fill="#000000"></path> </g></svg>',
  2806. category: "SERVER",
  2807. type: "input",
  2808. placeholder: "discord webhook url",
  2809. onChange: (value) => {
  2810. value = value.toString().trim();
  2811. this.kxsClient.discordWebhookUrl = value;
  2812. this.kxsClient.discordTracker.setWebhookUrl(value);
  2813. this.kxsClient.updateLocalStorage();
  2814. },
  2815. });
  2816. this.addOption(MECHANIC, {
  2817. label: "Custom Crosshair",
  2818. value: this.kxsClient.customCrosshair || "",
  2819. type: "input",
  2820. category: "MECHANIC",
  2821. icon: '<svg fill="#000000" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>crosshair</title> <path d="M30 14.75h-2.824c-0.608-5.219-4.707-9.318-9.874-9.921l-0.053-0.005v-2.824c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 2.824c-5.219 0.608-9.318 4.707-9.921 9.874l-0.005 0.053h-2.824c-0.69 0-1.25 0.56-1.25 1.25s0.56 1.25 1.25 1.25v0h2.824c0.608 5.219 4.707 9.318 9.874 9.921l0.053 0.005v2.824c0 0.69 0.56 1.25 1.25 1.25s1.25-0.56 1.25-1.25v0-2.824c5.219-0.608 9.318-4.707 9.921-9.874l0.005-0.053h2.824c0.69 0 1.25-0.56 1.25-1.25s-0.56-1.25-1.25-1.25v0zM17.25 24.624v-2.624c0-0.69-0.56-1.25-1.25-1.25s-1.25 0.56-1.25 1.25v0 2.624c-3.821-0.57-6.803-3.553-7.368-7.326l-0.006-0.048h2.624c0.69 0 1.25-0.56 1.25-1.25s-0.56-1.25-1.25-1.25v0h-2.624c0.57-3.821 3.553-6.804 7.326-7.368l0.048-0.006v2.624c0 0.69 0.56 1.25 1.25 1.25s1.25-0.56 1.25-1.25v0-2.624c3.821 0.57 6.803 3.553 7.368 7.326l0.006 0.048h-2.624c-0.69 0-1.25 0.56-1.25 1.25s0.56 1.25 1.25 1.25v0h2.624c-0.571 3.821-3.553 6.803-7.326 7.368l-0.048 0.006z"></path> </g></svg>',
  2822. placeholder: "URL of png,gif,svg",
  2823. onChange: (value) => {
  2824. this.kxsClient.customCrosshair = value;
  2825. this.kxsClient.updateLocalStorage();
  2826. this.kxsClient.hud.loadCustomCrosshair();
  2827. },
  2828. });
  2829. this.addOption(MECHANIC, {
  2830. label: "Heal Warning",
  2831. value: this.kxsClient.isHealthWarningEnabled,
  2832. type: "toggle",
  2833. category: "MECHANIC",
  2834. icon: '<svg viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>health</title> <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="add" fill="#000000" transform="translate(42.666667, 64.000000)"> <path d="M365.491733,234.665926 C339.947827,276.368766 302.121072,321.347032 252.011468,369.600724 L237.061717,383.7547 C234.512147,386.129148 231.933605,388.511322 229.32609,390.901222 L213.333333,405.333333 C205.163121,398.070922 197.253659,390.878044 189.604949,383.7547 L174.655198,369.600724 C124.545595,321.347032 86.7188401,276.368766 61.174934,234.665926 L112.222458,234.666026 C134.857516,266.728129 165.548935,301.609704 204.481843,339.08546 L213.333333,347.498667 L214.816772,346.115558 C257.264819,305.964102 290.400085,268.724113 314.444476,234.665648 L365.491733,234.665926 Z M149.333333,58.9638831 L213.333333,186.944 L245.333333,122.963883 L269.184,170.666667 L426.666667,170.666667 L426.666667,213.333333 L247.850667,213.333333 L213.333333,282.36945 L149.333333,154.368 L119.851392,213.333333 L3.55271368e-14,213.333333 L3.55271368e-14,170.666667 L93.4613333,170.666667 L149.333333,58.9638831 Z M290.133333,0 C353.756537,0 405.333333,51.5775732 405.333333,115.2 C405.333333,126.248908 404.101625,137.626272 401.63821,149.33209 L357.793994,149.332408 C360.62486,138.880112 362.217829,128.905378 362.584434,119.422244 L362.666667,115.2 C362.666667,75.1414099 330.192075,42.6666667 290.133333,42.6666667 C273.651922,42.6666667 258.124715,48.1376509 245.521279,58.0219169 L241.829932,61.1185374 L213.366947,86.6338354 L184.888885,61.1353673 C171.661383,49.2918281 154.669113,42.6666667 136.533333,42.6666667 C96.4742795,42.6666667 64,75.1409461 64,115.2 C64,125.932203 65.6184007,137.316846 68.8727259,149.332605 L25.028457,149.33209 C22.5650412,137.626272 21.3333333,126.248908 21.3333333,115.2 C21.3333333,51.5767968 72.9101302,0 136.533333,0 C166.046194,0 192.966972,11.098031 213.350016,29.348444 C233.716605,11.091061 260.629741,0 290.133333,0 Z" id="Combined-Shape"> </path> </g> </g> </g></svg>',
  2835. onChange: (value) => {
  2836. var _a, _b;
  2837. this.kxsClient.isHealthWarningEnabled = !this.kxsClient.isHealthWarningEnabled;
  2838. if (this.kxsClient.isHealthWarningEnabled) {
  2839. // Always enter placement mode when enabling from RSHIFT menu
  2840. (_a = this.kxsClient.healWarning) === null || _a === void 0 ? void 0 : _a.enableDragging();
  2841. }
  2842. else {
  2843. (_b = this.kxsClient.healWarning) === null || _b === void 0 ? void 0 : _b.hide();
  2844. }
  2845. this.kxsClient.updateLocalStorage();
  2846. },
  2847. });
  2848. this.addOption(SERVER, {
  2849. label: "Update Checker",
  2850. value: this.kxsClient.isAutoUpdateEnabled,
  2851. type: "toggle",
  2852. icon: '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M18.4721 16.7023C17.3398 18.2608 15.6831 19.3584 13.8064 19.7934C11.9297 20.2284 9.95909 19.9716 8.25656 19.0701C6.55404 18.1687 5.23397 16.6832 4.53889 14.8865C3.84381 13.0898 3.82039 11.1027 4.47295 9.29011C5.12551 7.47756 6.41021 5.96135 8.09103 5.02005C9.77184 4.07875 11.7359 3.77558 13.6223 4.16623C15.5087 4.55689 17.1908 5.61514 18.3596 7.14656C19.5283 8.67797 20.1052 10.5797 19.9842 12.5023M19.9842 12.5023L21.4842 11.0023M19.9842 12.5023L18.4842 11.0023" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M12 8V12L15 15" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>',
  2853. category: "SERVER",
  2854. onChange: (value) => {
  2855. this.kxsClient.isAutoUpdateEnabled = !this.kxsClient.isAutoUpdateEnabled;
  2856. this.kxsClient.updateLocalStorage();
  2857. },
  2858. });
  2859. this.addOption(MECHANIC, {
  2860. label: `Uncap FPS`,
  2861. value: this.kxsClient.isFpsUncapped,
  2862. type: "toggle",
  2863. icon: '<svg viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> <title>ic_fluent_fps_960_24_filled</title> <desc>Created with Sketch.</desc> <g id="🔍-Product-Icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="ic_fluent_fps_960_24_filled" fill="#000000" fill-rule="nonzero"> <path d="M11.75,15 C12.9926407,15 14,16.0073593 14,17.25 C14,18.440864 13.0748384,19.4156449 11.9040488,19.4948092 L11.75,19.5 L11,19.5 L11,21.25 C11,21.6296958 10.7178461,21.943491 10.3517706,21.9931534 L10.25,22 C9.87030423,22 9.55650904,21.7178461 9.50684662,21.3517706 L9.5,21.25 L9.5,15.75 C9.5,15.3703042 9.78215388,15.056509 10.1482294,15.0068466 L10.25,15 L11.75,15 Z M18,15 C19.1045695,15 20,15.8954305 20,17 C20,17.4142136 19.6642136,17.75 19.25,17.75 C18.8703042,17.75 18.556509,17.4678461 18.5068466,17.1017706 L18.5,17 C18.5,16.7545401 18.3231248,16.5503916 18.0898756,16.5080557 L18,16.5 L17.375,16.5 C17.029822,16.5 16.75,16.779822 16.75,17.125 C16.75,17.4387982 16.9812579,17.6985831 17.2826421,17.7432234 L17.375,17.75 L17.875,17.75 C19.0486051,17.75 20,18.7013949 20,19.875 C20,20.9975788 19.1295366,21.91685 18.0267588,21.9946645 L17.875,22 L17.25,22 C16.1454305,22 15.25,21.1045695 15.25,20 C15.25,19.5857864 15.5857864,19.25 16,19.25 C16.3796958,19.25 16.693491,19.5321539 16.7431534,19.8982294 L16.75,20 C16.75,20.2454599 16.9268752,20.4496084 17.1601244,20.4919443 L17.25,20.5 L17.875,20.5 C18.220178,20.5 18.5,20.220178 18.5,19.875 C18.5,19.5612018 18.2687421,19.3014169 17.9673579,19.2567766 L17.875,19.25 L17.375,19.25 C16.2013949,19.25 15.25,18.2986051 15.25,17.125 C15.25,16.0024212 16.1204634,15.08315 17.2232412,15.0053355 L17.375,15 L18,15 Z M7.75,15 C8.16421356,15 8.5,15.3357864 8.5,15.75 C8.5,16.1296958 8.21784612,16.443491 7.85177056,16.4931534 L7.75,16.5 L5.5,16.4990964 L5.5,18.0020964 L7.25,18.002809 C7.66421356,18.002809 8,18.3385954 8,18.752809 C8,19.1325047 7.71784612,19.4462999 7.35177056,19.4959623 L7.25,19.502809 L5.5,19.5020964 L5.5,21.2312276 C5.5,21.6109234 5.21784612,21.9247186 4.85177056,21.974381 L4.75,21.9812276 C4.37030423,21.9812276 4.05650904,21.6990738 4.00684662,21.3329982 L4,21.2312276 L4,15.75 C4,15.3703042 4.28215388,15.056509 4.64822944,15.0068466 L4.75,15 L7.75,15 Z M11.75,16.5 L11,16.5 L11,18 L11.75,18 C12.1642136,18 12.5,17.6642136 12.5,17.25 C12.5,16.8703042 12.2178461,16.556509 11.8517706,16.5068466 L11.75,16.5 Z M5,3 C6.65685425,3 8,4.34314575 8,6 L7.99820112,6.1048763 L8,6.15469026 L8,10 C8,11.5976809 6.75108004,12.9036609 5.17627279,12.9949073 L5,13 L4.7513884,13 C3.23183855,13 2,11.7681615 2,10.2486116 C2,9.69632685 2.44771525,9.2486116 3,9.2486116 C3.51283584,9.2486116 3.93550716,9.63465179 3.99327227,10.1319905 L4,10.2486116 C4,10.6290103 4.28267621,10.9433864 4.64942945,10.9931407 L4.7513884,11 L5,11 C5.51283584,11 5.93550716,10.6139598 5.99327227,10.1166211 L6,10 L5.99991107,8.82932572 C5.68715728,8.93985718 5.35060219,9 5,9 C3.34314575,9 2,7.65685425 2,6 C2,4.34314575 3.34314575,3 5,3 Z M12.2512044,3 C13.7707542,3 15.0025928,4.23183855 15.0025928,5.7513884 C15.0025928,6.30367315 14.5548775,6.7513884 14.0025928,6.7513884 C13.489757,6.7513884 13.0670856,6.36534821 13.0093205,5.86800953 L13.0025928,5.7513884 C13.0025928,5.37098974 12.7199166,5.05661365 12.3531633,5.00685929 L12.2512044,5 L12.0025928,5 C11.489757,5 11.0670856,5.38604019 11.0093205,5.88337887 L11.0025928,6 L11.0026817,7.17067428 C11.3154355,7.06014282 11.6519906,7 12.0025928,7 C13.659447,7 15.0025928,8.34314575 15.0025928,10 C15.0025928,11.6568542 13.659447,13 12.0025928,13 C10.3457385,13 9.0025928,11.6568542 9.0025928,10 L9.00441213,9.89453033 L9.0025928,9.84530974 L9.0025928,6 C9.0025928,4.40231912 10.2515128,3.09633912 11.82632,3.00509269 L12.0025928,3 L12.2512044,3 Z M19,3 C20.5976809,3 21.9036609,4.24891996 21.9949073,5.82372721 L22,6 L22,10 C22,11.6568542 20.6568542,13 19,13 C17.4023191,13 16.0963391,11.75108 16.0050927,10.1762728 L16,10 L16,6 C16,4.34314575 17.3431458,3 19,3 Z M12.0025928,9 C11.450308,9 11.0025928,9.44771525 11.0025928,10 C11.0025928,10.5522847 11.450308,11 12.0025928,11 C12.5548775,11 13.0025928,10.5522847 13.0025928,10 C13.0025928,9.44771525 12.5548775,9 12.0025928,9 Z M19,5 C18.4871642,5 18.0644928,5.38604019 18.0067277,5.88337887 L18,6 L18,10 C18,10.5522847 18.4477153,11 19,11 C19.5128358,11 19.9355072,10.6139598 19.9932723,10.1166211 L20,10 L20,6 C20,5.44771525 19.5522847,5 19,5 Z M5,5 C4.44771525,5 4,5.44771525 4,6 C4,6.55228475 4.44771525,7 5,7 C5.55228475,7 6,6.55228475 6,6 C6,5.44771525 5.55228475,5 5,5 Z" id="🎨Color"> </path> </g> </g> </g></svg>',
  2864. category: 'MECHANIC',
  2865. onChange: () => {
  2866. this.kxsClient.isFpsUncapped = !this.kxsClient.isFpsUncapped;
  2867. this.kxsClient.setAnimationFrameCallback();
  2868. this.kxsClient.updateLocalStorage();
  2869. },
  2870. });
  2871. this.addOption(HUD, {
  2872. label: `Winning Animation`,
  2873. value: this.kxsClient.isWinningAnimationEnabled,
  2874. icon: '<svg fill="#000000" height="200px" width="200px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 448.881 448.881" xml:space="preserve"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <path d="M189.82,138.531c-8.92,0-16.611,6.307-18.353,15.055l-11.019,55.306c-3.569,20.398-7.394,40.53-9.946,59.652h-0.513 c-2.543-19.122-5.35-37.474-9.176-57.615l-11.85-62.35c-1.112-5.832-6.206-10.048-12.139-10.048H95.819 c-5.854,0-10.909,4.114-12.099,9.853l-12.497,60.507c-4.332,21.159-8.414,41.805-11.213,60.413h-0.513 c-2.8-17.332-6.369-39.511-10.196-59.901l-10.024-54.643c-1.726-9.403-9.922-16.23-19.479-16.23c-6.05,0-11.774,2.77-15.529,7.52 c-3.755,4.751-5.133,10.965-3.733,16.851l32.747,137.944c1.322,5.568,6.299,9.503,12.022,9.503h22.878 c5.792,0,10.809-4.028,12.061-9.689l14.176-64.241c4.083-17.334,6.883-33.648,9.946-53.019h0.507 c2.037,19.627,4.845,35.685,8.157,53.019l12.574,63.96c1.136,5.794,6.222,9.97,12.125,9.97h22.325 c5.638,0,10.561-3.811,11.968-9.269l35.919-139.158c1.446-5.607,0.225-11.564-3.321-16.136 C201.072,141.207,195.612,138.531,189.82,138.531z"></path> <path d="M253.516,138.531c-10.763,0-19.495,8.734-19.495,19.503v132.821c0,10.763,8.732,19.495,19.495,19.495 c10.771,0,19.503-8.732,19.503-19.495V158.034C273.019,147.265,264.287,138.531,253.516,138.531z"></path> <path d="M431.034,138.531c-9.861,0-17.847,7.995-17.847,17.847v32.373c0,25.748,0.761,48.945,3.313,71.637h-0.763 c-7.652-19.379-17.847-40.786-28.041-58.891l-32.14-56.704c-2.193-3.865-6.299-6.26-10.747-6.26h-25.818 c-6.827,0-12.357,5.529-12.357,12.357v141.615c0,9.86,7.987,17.847,17.847,17.847c9.853,0,17.84-7.987,17.84-17.847v-33.905 c0-28.042-0.514-52.258-1.532-74.941l0.769-0.256c8.406,20.141,19.627,42.318,29.823,60.671l33.174,59.909 c2.177,3.927,6.321,6.369,10.809,6.369h21.159c6.828,0,12.357-5.53,12.357-12.357V156.378 C448.881,146.526,440.894,138.531,431.034,138.531z"></path> </g> </g></svg>',
  2875. category: "HUD",
  2876. type: "toggle",
  2877. onChange: () => {
  2878. this.kxsClient.isWinningAnimationEnabled = !this.kxsClient.isWinningAnimationEnabled;
  2879. this.kxsClient.updateLocalStorage();
  2880. },
  2881. });
  2882. this.addOption(HUD, {
  2883. label: `Spotify Player`,
  2884. value: this.kxsClient.isSpotifyPlayerEnabled,
  2885. icon: '<svg fill="#000000" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>spotify</title> <path d="M24.849 14.35c-3.206-1.616-6.988-2.563-10.991-2.563-2.278 0-4.484 0.306-6.58 0.881l0.174-0.041c-0.123 0.040-0.265 0.063-0.412 0.063-0.76 0-1.377-0.616-1.377-1.377 0-0.613 0.401-1.132 0.954-1.311l0.010-0.003c5.323-1.575 14.096-1.275 19.646 2.026 0.426 0.258 0.706 0.719 0.706 1.245 0 0.259-0.068 0.502-0.186 0.712l0.004-0.007c-0.29 0.345-0.721 0.563-1.204 0.563-0.273 0-0.529-0.070-0.752-0.192l0.008 0.004zM24.699 18.549c-0.201 0.332-0.561 0.55-0.971 0.55-0.225 0-0.434-0.065-0.61-0.178l0.005 0.003c-2.739-1.567-6.021-2.49-9.518-2.49-1.925 0-3.784 0.28-5.539 0.801l0.137-0.035c-0.101 0.032-0.217 0.051-0.337 0.051-0.629 0-1.139-0.51-1.139-1.139 0-0.509 0.333-0.939 0.793-1.086l0.008-0.002c1.804-0.535 3.878-0.843 6.023-0.843 3.989 0 7.73 1.064 10.953 2.925l-0.106-0.056c0.297 0.191 0.491 0.52 0.491 0.894 0 0.227-0.071 0.437-0.192 0.609l0.002-0.003zM22.899 22.673c-0.157 0.272-0.446 0.452-0.777 0.452-0.186 0-0.359-0.057-0.502-0.154l0.003 0.002c-2.393-1.346-5.254-2.139-8.299-2.139-1.746 0-3.432 0.261-5.020 0.745l0.122-0.032c-0.067 0.017-0.145 0.028-0.224 0.028-0.512 0-0.927-0.415-0.927-0.927 0-0.432 0.296-0.795 0.696-0.898l0.006-0.001c1.581-0.47 3.397-0.74 5.276-0.74 3.402 0 6.596 0.886 9.366 2.44l-0.097-0.050c0.302 0.15 0.506 0.456 0.506 0.809 0 0.172-0.048 0.333-0.132 0.469l0.002-0.004zM16 1.004c0 0 0 0-0 0-8.282 0-14.996 6.714-14.996 14.996s6.714 14.996 14.996 14.996c8.282 0 14.996-6.714 14.996-14.996v0c-0.025-8.272-6.724-14.971-14.993-14.996h-0.002z"></path> </g></svg>',
  2886. category: "HUD",
  2887. type: "toggle",
  2888. onChange: () => {
  2889. this.kxsClient.isSpotifyPlayerEnabled = !this.kxsClient.isSpotifyPlayerEnabled;
  2890. this.kxsClient.updateLocalStorage();
  2891. this.kxsClient.toggleSpotifyMenu();
  2892. },
  2893. });
  2894. this.addOption(HUD, {
  2895. label: "Kill Feed Chroma",
  2896. value: this.kxsClient.isKillFeedBlint,
  2897. icon: `<svg fill="#000000" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title></title> <g data-name="Layer 2" id="Layer_2"> <path d="M18,11a1,1,0,0,1-1,1,5,5,0,0,0-5,5,1,1,0,0,1-2,0,5,5,0,0,0-5-5,1,1,0,0,1,0-2,5,5,0,0,0,5-5,1,1,0,0,1,2,0,5,5,0,0,0,5,5A1,1,0,0,1,18,11Z"></path> <path d="M19,24a1,1,0,0,1-1,1,2,2,0,0,0-2,2,1,1,0,0,1-2,0,2,2,0,0,0-2-2,1,1,0,0,1,0-2,2,2,0,0,0,2-2,1,1,0,0,1,2,0,2,2,0,0,0,2,2A1,1,0,0,1,19,24Z"></path> <path d="M28,17a1,1,0,0,1-1,1,4,4,0,0,0-4,4,1,1,0,0,1-2,0,4,4,0,0,0-4-4,1,1,0,0,1,0-2,4,4,0,0,0,4-4,1,1,0,0,1,2,0,4,4,0,0,0,4,4A1,1,0,0,1,28,17Z"></path> </g> </g></svg>`,
  2898. category: "HUD",
  2899. type: "toggle",
  2900. onChange: () => {
  2901. this.kxsClient.isKillFeedBlint = !this.kxsClient.isKillFeedBlint;
  2902. this.kxsClient.updateLocalStorage();
  2903. this.kxsClient.hud.toggleKillFeed();
  2904. },
  2905. });
  2906. this.addOption(SERVER, {
  2907. label: `Rich Presence (Account token required)`,
  2908. value: this.kxsClient.discordToken || "",
  2909. icon: '<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M18.59 5.88997C17.36 5.31997 16.05 4.89997 14.67 4.65997C14.5 4.95997 14.3 5.36997 14.17 5.69997C12.71 5.47997 11.26 5.47997 9.83001 5.69997C9.69001 5.36997 9.49001 4.95997 9.32001 4.65997C7.94001 4.89997 6.63001 5.31997 5.40001 5.88997C2.92001 9.62997 2.25001 13.28 2.58001 16.87C4.23001 18.1 5.82001 18.84 7.39001 19.33C7.78001 18.8 8.12001 18.23 8.42001 17.64C7.85001 17.43 7.31001 17.16 6.80001 16.85C6.94001 16.75 7.07001 16.64 7.20001 16.54C10.33 18 13.72 18 16.81 16.54C16.94 16.65 17.07 16.75 17.21 16.85C16.7 17.16 16.15 17.42 15.59 17.64C15.89 18.23 16.23 18.8 16.62 19.33C18.19 18.84 19.79 18.1 21.43 16.87C21.82 12.7 20.76 9.08997 18.61 5.88997H18.59ZM8.84001 14.67C7.90001 14.67 7.13001 13.8 7.13001 12.73C7.13001 11.66 7.88001 10.79 8.84001 10.79C9.80001 10.79 10.56 11.66 10.55 12.73C10.55 13.79 9.80001 14.67 8.84001 14.67ZM15.15 14.67C14.21 14.67 13.44 13.8 13.44 12.73C13.44 11.66 14.19 10.79 15.15 10.79C16.11 10.79 16.87 11.66 16.86 12.73C16.86 13.79 16.11 14.67 15.15 14.67Z" fill="#000000"></path> </g></svg>',
  2910. category: "SERVER",
  2911. type: "input",
  2912. placeholder: "Your discord account token",
  2913. onChange: (value) => {
  2914. value = value.toString().trim();
  2915. this.kxsClient.discordToken = this.kxsClient.parseToken(value);
  2916. this.kxsClient.discordRPC.disconnect();
  2917. this.kxsClient.discordRPC.connect();
  2918. this.kxsClient.updateLocalStorage();
  2919. },
  2920. });
  2921. this.addOption(MECHANIC, {
  2922. label: `Kill Leader Tracking`,
  2923. icon: '<svg fill="#000000" viewBox="-4 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>crown</title> <path d="M12 10.938c-1.375 0-2.5-1.125-2.5-2.5 0-1.406 1.125-2.5 2.5-2.5s2.5 1.094 2.5 2.5c0 1.375-1.125 2.5-2.5 2.5zM2.031 9.906c1.094 0 1.969 0.906 1.969 2 0 1.125-0.875 2-1.969 2-1.125 0-2.031-0.875-2.031-2 0-1.094 0.906-2 2.031-2zM22.031 9.906c1.094 0 1.969 0.906 1.969 2 0 1.125-0.875 2-1.969 2-1.125 0-2.031-0.875-2.031-2 0-1.094 0.906-2 2.031-2zM4.219 23.719l-1.656-9.063c0.5-0.094 0.969-0.375 1.344-0.688 1.031 0.938 2.344 1.844 3.594 1.844 1.5 0 2.719-2.313 3.563-4.25 0.281 0.094 0.625 0.188 0.938 0.188s0.656-0.094 0.938-0.188c0.844 1.938 2.063 4.25 3.563 4.25 1.25 0 2.563-0.906 3.594-1.844 0.375 0.313 0.844 0.594 1.344 0.688l-1.656 9.063h-15.563zM3.875 24.5h16.25v1.531h-16.25v-1.531z"></path> </g></svg>',
  2924. category: "MECHANIC",
  2925. value: this.kxsClient.isKillLeaderTrackerEnabled,
  2926. type: "toggle",
  2927. onChange: (value) => {
  2928. this.kxsClient.isKillLeaderTrackerEnabled = !this.kxsClient.isKillLeaderTrackerEnabled;
  2929. this.kxsClient.updateLocalStorage();
  2930. },
  2931. });
  2932. this.addOption(MECHANIC, {
  2933. label: `Friends Detector (separe with ',')`,
  2934. icon: '<svg fill="#000000" viewBox="0 -6 44 44" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M42.001,32.000 L14.010,32.000 C12.908,32.000 12.010,31.104 12.010,30.001 L12.010,28.002 C12.010,27.636 12.211,27.300 12.532,27.124 L22.318,21.787 C19.040,18.242 19.004,13.227 19.004,12.995 L19.010,7.002 C19.010,6.946 19.015,6.891 19.024,6.837 C19.713,2.751 24.224,0.007 28.005,0.007 C28.006,0.007 28.008,0.007 28.009,0.007 C31.788,0.007 36.298,2.749 36.989,6.834 C36.998,6.889 37.003,6.945 37.003,7.000 L37.006,12.994 C37.006,13.225 36.970,18.240 33.693,21.785 L43.479,27.122 C43.800,27.298 44.000,27.634 44.000,28.000 L44.000,30.001 C44.000,31.104 43.103,32.000 42.001,32.000 ZM31.526,22.880 C31.233,22.720 31.039,22.425 31.008,22.093 C30.978,21.761 31.116,21.436 31.374,21.226 C34.971,18.310 35.007,13.048 35.007,12.995 L35.003,7.089 C34.441,4.089 30.883,2.005 28.005,2.005 C25.126,2.006 21.570,4.091 21.010,7.091 L21.004,12.997 C21.004,13.048 21.059,18.327 24.636,21.228 C24.895,21.438 25.033,21.763 25.002,22.095 C24.972,22.427 24.778,22.722 24.485,22.882 L14.010,28.596 L14.010,30.001 L41.999,30.001 L42.000,28.595 L31.526,22.880 ZM18.647,2.520 C17.764,2.177 16.848,1.997 15.995,1.997 C13.116,1.998 9.559,4.083 8.999,7.083 L8.993,12.989 C8.993,13.041 9.047,18.319 12.625,21.220 C12.884,21.430 13.022,21.755 12.992,22.087 C12.961,22.419 12.767,22.714 12.474,22.874 L1.999,28.588 L1.999,29.993 L8.998,29.993 C9.550,29.993 9.997,30.441 9.997,30.993 C9.997,31.545 9.550,31.993 8.998,31.993 L1.999,31.993 C0.897,31.993 -0.000,31.096 -0.000,29.993 L-0.000,27.994 C-0.000,27.629 0.200,27.292 0.521,27.117 L10.307,21.779 C7.030,18.234 6.993,13.219 6.993,12.988 L6.999,6.994 C6.999,6.939 7.004,6.883 7.013,6.829 C7.702,2.744 12.213,-0.000 15.995,-0.000 C15.999,-0.000 16.005,-0.000 16.010,-0.000 C17.101,-0.000 18.262,0.227 19.369,0.656 C19.885,0.856 20.140,1.435 19.941,1.949 C19.740,2.464 19.158,2.720 18.647,2.520 Z"></path> </g></svg>',
  2935. category: "MECHANIC",
  2936. value: this.kxsClient.all_friends,
  2937. type: "input",
  2938. placeholder: "kisakay,iletal...",
  2939. onChange: (value) => {
  2940. this.kxsClient.all_friends = value;
  2941. this.kxsClient.updateLocalStorage();
  2942. },
  2943. });
  2944. this.addOption(HUD, {
  2945. label: `Change Background`,
  2946. icon: '<svg height="200px" width="200px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 179.006 179.006" xml:space="preserve" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <g> <polygon style="fill:#010002;" points="20.884,116.354 11.934,116.354 11.934,32.818 137.238,32.818 137.238,41.768 149.172,41.768 149.172,20.884 0,20.884 0,128.288 20.884,128.288 "></polygon> <path style="fill:#010002;" d="M29.834,50.718v107.404h149.172V50.718H29.834z M123.58,136.856c-0.024,0-0.048,0-0.072,0 c-0.012,0-1.187,0-2.81,0c-3.795,0-10.078,0-10.114,0c-19.625,0-39.25,0-58.875,0v-3.473c0.907-0.859,2.005-1.551,3.168-2.166 c1.981-1.062,3.938-2.148,5.967-3.115c1.957-0.937,3.998-1.742,6.003-2.59c1.886-0.8,3.801-1.545,5.674-2.363 c0.328-0.137,0.638-0.489,0.776-0.811c0.424-1.05,0.782-2.124,1.116-3.216c0.245-0.823,0.412-1.635,1.468-1.862 c0.263-0.048,0.597-0.513,0.627-0.817c0.209-1.581,0.37-3.168,0.489-4.744c0.024-0.346-0.149-0.776-0.382-1.038 c-1.384-1.557-2.142-3.353-2.47-5.406c-0.161-1.038-0.74-1.993-1.038-3.013c-0.394-1.366-0.728-2.745-1.038-4.129 c-0.119-0.501-0.048-1.038-0.125-1.551c-0.125-0.746-0.107-1.319,0.806-1.611c0.233-0.084,0.442-0.668,0.453-1.032 c0.048-2.214,0.012-4.433,0.024-6.641c0.012-1.36,0-2.727,0.107-4.087c0.185-2.596,1.718-4.421,3.622-5.997 c2.787-2.303,6.128-3.377,9.565-4.189c1.808-0.424,3.64-0.68,5.478-0.979c0.489-0.078,0.996-0.006,1.498-0.006 c0.095,0.125,0.161,0.251,0.251,0.37c-0.376,0.28-0.811,0.513-1.134,0.847c-0.746,0.746-0.674,1.265,0.125,1.945 c1.647,1.396,3.318,2.804,4.911,4.254c1.42,1.271,1.969,2.942,1.981,4.815c0,3.222,0,6.45,0,9.672c0,0.65-0.048,1.313,0.776,1.605 c0.167,0.066,0.352,0.424,0.34,0.632c-0.131,1.641-0.322,3.294-0.489,4.941c-0.006,0.066-0.018,0.131-0.054,0.185 c-1.486,2.166-1.677,4.827-2.733,7.148c-0.048,0.09-0.078,0.191-0.125,0.257c-1.969,2.315-1.36,5.102-1.396,7.769 c0,0.269,0.197,0.686,0.406,0.782c0.806,0.358,1.002,1.044,1.223,1.772c0.352,1.14,0.692,2.303,1.181,3.389 c0.179,0.394,0.716,0.746,1.17,0.907c0.943,0.364,1.886,0.74,2.834,1.11c2.363-1.002,5.734-2.434,6.385-2.727 c0.919-0.418,1.611-1.349,2.44-1.993c0.37-0.28,0.817-0.537,1.259-0.615c1.504-0.239,2.16-0.77,2.518-2.255 c0.465-1.945,0.806-3.89,0.388-5.913c-0.167-0.877-0.489-1.45-1.366-1.784c-1.778-0.698-3.532-1.474-5.293-2.22 c-1.319-0.555-1.396-1.02-0.919-2.387c1.516-4.296,2.631-8.658,3.007-13.258c0.28-3.443,0.048-6.981,1.307-10.305 c0.871-2.339,2.339-4.505,4.696-5.203c1.796-0.531,3.359-1.742,5.269-1.999c0.358-0.018,0.674-0.072,1.026-0.054 c0.042,0.006,0.078,0.012,0.113,0.012c4.529,0.286,9.923,3.019,11.2,8.043c0.066,0.257,0.101,0.525,0.143,0.788h0.125 c0.698,2.852,0.621,5.818,0.859,8.712c0.37,4.594,1.504,8.962,3.019,13.264c0.477,1.366,0.394,1.832-0.919,2.381 c-1.76,0.746-3.514,1.522-5.299,2.22c-0.871,0.34-1.181,0.895-1.36,1.784c-0.406,2.029-0.084,3.968,0.388,5.913 c0.346,1.48,1.014,2.011,2.512,2.25c0.442,0.078,0.883,0.334,1.259,0.615c0.829,0.644,1.516,1.569,2.44,1.993 c3.234,1.468,6.51,2.888,9.839,4.117c5.114,1.88,8.509,5.478,9.326,11.045C145.944,136.856,134.768,136.856,123.58,136.856z"></path> </g> </g> </g></svg>',
  2947. category: "HUD",
  2948. value: true,
  2949. type: "click",
  2950. onChange: () => {
  2951. const backgroundElement = document.getElementById("background");
  2952. if (!backgroundElement) {
  2953. alert("Element with id 'background' not found.");
  2954. return;
  2955. }
  2956. const choice = prompt("Enter '0' to default Kxs background, '1' to provide a URL or '2' to upload a local image:");
  2957. if (choice === "0") {
  2958. localStorage.removeItem("lastBackgroundUrl");
  2959. localStorage.removeItem("lastBackgroundFile");
  2960. localStorage.removeItem("lastBackgroundType");
  2961. localStorage.removeItem("lastBackgroundValue");
  2962. backgroundElement.style.backgroundImage = `url(${background_image})`;
  2963. }
  2964. else if (choice === "1") {
  2965. const newBackgroundUrl = prompt("Enter the URL of the new background image:");
  2966. if (newBackgroundUrl) {
  2967. backgroundElement.style.backgroundImage = `url(${newBackgroundUrl})`;
  2968. this.kxsClient.saveBackgroundToLocalStorage(newBackgroundUrl);
  2969. alert("Background updated successfully!");
  2970. }
  2971. }
  2972. else if (choice === "2") {
  2973. const fileInput = document.createElement("input");
  2974. fileInput.type = "file";
  2975. fileInput.accept = "image/*";
  2976. fileInput.onchange = (event) => {
  2977. var _a, _b;
  2978. const file = (_b = (_a = event.target) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b[0];
  2979. if (file) {
  2980. const reader = new FileReader();
  2981. reader.onload = () => {
  2982. backgroundElement.style.backgroundImage = `url(${reader.result})`;
  2983. this.kxsClient.saveBackgroundToLocalStorage(file);
  2984. alert("Background updated successfully!");
  2985. };
  2986. reader.readAsDataURL(file);
  2987. }
  2988. };
  2989. fileInput.click();
  2990. }
  2991. },
  2992. });
  2993. }
  2994. createOptionCard(option, container) {
  2995. const optionCard = document.createElement("div");
  2996. Object.assign(optionCard.style, {
  2997. background: "rgba(31, 41, 55, 0.8)",
  2998. borderRadius: "10px",
  2999. padding: "16px",
  3000. display: "flex",
  3001. flexDirection: "column",
  3002. alignItems: "center",
  3003. gap: "12px",
  3004. minHeight: "150px",
  3005. });
  3006. const iconContainer = document.createElement("div");
  3007. Object.assign(iconContainer.style, {
  3008. width: "48px",
  3009. height: "48px",
  3010. borderRadius: "50%",
  3011. display: "flex",
  3012. alignItems: "center",
  3013. justifyContent: "center",
  3014. marginBottom: "8px"
  3015. });
  3016. iconContainer.innerHTML = option.icon || '';
  3017. const title = document.createElement("div");
  3018. title.textContent = option.label;
  3019. title.style.fontSize = "16px";
  3020. title.style.textAlign = "center";
  3021. let control = null;
  3022. switch (option.type) {
  3023. case "info":
  3024. control = this.createInfoElement(option);
  3025. break;
  3026. case "input":
  3027. control = this.createInputElement(option);
  3028. break;
  3029. case "toggle":
  3030. control = this.createToggleButton(option);
  3031. break;
  3032. case "sub":
  3033. control = this.createSubButton(option);
  3034. break;
  3035. case "click":
  3036. control = this.createClickButton(option);
  3037. }
  3038. optionCard.appendChild(iconContainer);
  3039. optionCard.appendChild(title);
  3040. optionCard.appendChild(control);
  3041. container.appendChild(optionCard);
  3042. }
  3043. setActiveCategory(category) {
  3044. this.activeCategory = category;
  3045. this.filterOptions();
  3046. // Update button styles
  3047. this.menu.querySelectorAll('.category-btn').forEach(btn => {
  3048. const btnCategory = btn.dataset.category;
  3049. btn.style.background =
  3050. btnCategory === category ? '#3B82F6' : 'rgba(55, 65, 81, 0.8)';
  3051. });
  3052. }
  3053. filterOptions() {
  3054. const gridContainer = document.getElementById('kxsMenuGrid');
  3055. if (gridContainer) {
  3056. // Clear existing content
  3057. gridContainer.innerHTML = '';
  3058. // Get unique options based on category and search term
  3059. const displayedOptions = new Set();
  3060. this.sections.forEach(section => {
  3061. if (this.activeCategory === 'ALL' || section.category === this.activeCategory) {
  3062. section.options.forEach(option => {
  3063. // Create a unique key for each option
  3064. const optionKey = `${option.label}-${option.category}`;
  3065. // Check if option matches search term
  3066. const matchesSearch = this.searchTerm === '' ||
  3067. option.label.toLowerCase().includes(this.searchTerm) ||
  3068. option.category.toLowerCase().includes(this.searchTerm);
  3069. if (!displayedOptions.has(optionKey) && matchesSearch) {
  3070. displayedOptions.add(optionKey);
  3071. this.createOptionCard(option, gridContainer);
  3072. }
  3073. });
  3074. }
  3075. });
  3076. // Show a message if no options match the search
  3077. if (displayedOptions.size === 0 && this.searchTerm !== '') {
  3078. const noResultsMsg = document.createElement('div');
  3079. noResultsMsg.textContent = `No results found for "${this.searchTerm}"`;
  3080. noResultsMsg.style.gridColumn = '1 / -1';
  3081. noResultsMsg.style.textAlign = 'center';
  3082. noResultsMsg.style.padding = '20px';
  3083. noResultsMsg.style.color = '#9CA3AF';
  3084. gridContainer.appendChild(noResultsMsg);
  3085. }
  3086. }
  3087. }
  3088. createGridContainer() {
  3089. const gridContainer = document.createElement("div");
  3090. const isMobile = this.kxsClient.isMobile && this.kxsClient.isMobile();
  3091. Object.assign(gridContainer.style, {
  3092. display: "grid",
  3093. gridTemplateColumns: isMobile ? "repeat(4, 1fr)" : "repeat(3, 1fr)",
  3094. gap: isMobile ? "5px" : "16px",
  3095. padding: isMobile ? "2px" : "16px",
  3096. gridAutoRows: isMobile ? "minmax(38px, auto)" : "minmax(150px, auto)",
  3097. overflowY: "auto",
  3098. overflowX: "hidden", // Prevent horizontal scrolling
  3099. maxHeight: isMobile ? "28vh" : "calc(3 * 150px + 2 * 16px)",
  3100. width: "100%",
  3101. boxSizing: "border-box" // Include padding in width calculation
  3102. });
  3103. gridContainer.id = "kxsMenuGrid";
  3104. this.menu.appendChild(gridContainer);
  3105. }
  3106. addOption(section, option) {
  3107. section.options.push(option);
  3108. // Store all options for searching
  3109. this.allOptions.push(option);
  3110. }
  3111. addSection(title, category = "ALL") {
  3112. const section = {
  3113. title,
  3114. options: [],
  3115. category
  3116. };
  3117. const sectionElement = document.createElement("div");
  3118. sectionElement.className = "menu-section";
  3119. sectionElement.style.display = this.activeCategory === "ALL" || this.activeCategory === category ? "block" : "none";
  3120. section.element = sectionElement;
  3121. this.sections.push(section);
  3122. this.menu.appendChild(sectionElement);
  3123. return section;
  3124. }
  3125. createToggleButton(option) {
  3126. const btn = document.createElement("button");
  3127. const isMobile = this.kxsClient.isMobile && this.kxsClient.isMobile();
  3128. Object.assign(btn.style, {
  3129. width: "100%",
  3130. padding: isMobile ? "2px 0px" : "8px",
  3131. height: isMobile ? "24px" : "auto",
  3132. background: option.value ? "#059669" : "#DC2626",
  3133. border: "none",
  3134. borderRadius: isMobile ? "3px" : "6px",
  3135. color: "white",
  3136. cursor: "pointer",
  3137. transition: "background 0.2s",
  3138. fontSize: isMobile ? "9px" : "14px",
  3139. fontWeight: "bold",
  3140. minHeight: isMobile ? "20px" : "unset",
  3141. letterSpacing: isMobile ? "0.5px" : "1px"
  3142. });
  3143. btn.textContent = option.value ? "ENABLED" : "DISABLED";
  3144. btn.addEventListener("click", () => {
  3145. var _a;
  3146. const newValue = !option.value;
  3147. option.value = newValue;
  3148. btn.textContent = newValue ? "ENABLED" : "DISABLED";
  3149. btn.style.background = newValue ? "#059669" : "#DC2626";
  3150. (_a = option.onChange) === null || _a === void 0 ? void 0 : _a.call(option, newValue);
  3151. });
  3152. this.blockMousePropagation(btn);
  3153. return btn;
  3154. }
  3155. createClickButton(option) {
  3156. const btn = document.createElement("button");
  3157. const isMobile = this.kxsClient.isMobile && this.kxsClient.isMobile();
  3158. Object.assign(btn.style, {
  3159. width: "100%",
  3160. padding: isMobile ? "2px 0px" : "8px",
  3161. height: isMobile ? "24px" : "auto",
  3162. background: "#3B82F6",
  3163. border: "none",
  3164. borderRadius: "6px",
  3165. color: "white",
  3166. cursor: "pointer",
  3167. transition: "background 0.2s",
  3168. fontSize: "14px",
  3169. fontWeight: "bold"
  3170. });
  3171. btn.textContent = option.label;
  3172. btn.addEventListener("click", () => {
  3173. var _a;
  3174. (_a = option.onChange) === null || _a === void 0 ? void 0 : _a.call(option, true);
  3175. });
  3176. this.blockMousePropagation(btn);
  3177. return btn;
  3178. }
  3179. addShiftListener() {
  3180. // Gestionnaire pour la touche Shift (ouverture du menu)
  3181. window.addEventListener("keydown", (event) => {
  3182. if (event.key === "Shift" && event.location == 2) {
  3183. this.clearMenu();
  3184. this.toggleMenuVisibility();
  3185. // Ensure options are displayed after loading
  3186. this.filterOptions();
  3187. }
  3188. });
  3189. // Gestionnaire séparé pour la touche Échap avec capture en phase de capture
  3190. // pour intercepter l'événement avant qu'il n'atteigne le jeu
  3191. document.addEventListener("keydown", (event) => {
  3192. if (event.key === "Escape" && this.isClientMenuVisible) {
  3193. // Fermer le menu si la touche Échap est pressée et que le menu est visible
  3194. this.toggleMenuVisibility();
  3195. // Empêcher la propagation ET l'action par défaut
  3196. event.stopPropagation();
  3197. event.preventDefault();
  3198. // Arrêter la propagation de l'événement
  3199. return false;
  3200. }
  3201. }, true); // true = phase de capture
  3202. }
  3203. createInputElement(option) {
  3204. const input = document.createElement("input");
  3205. input.type = "text";
  3206. input.value = String(option.value);
  3207. if (option.placeholder) {
  3208. input.placeholder = option.placeholder;
  3209. }
  3210. Object.assign(input.style, {
  3211. width: "100%",
  3212. padding: "8px",
  3213. background: "rgba(55, 65, 81, 0.8)",
  3214. border: "none",
  3215. borderRadius: "6px",
  3216. color: "#FFAE00",
  3217. fontSize: "14px"
  3218. });
  3219. input.addEventListener("change", () => {
  3220. var _a;
  3221. option.value = input.value;
  3222. (_a = option.onChange) === null || _a === void 0 ? void 0 : _a.call(option, input.value);
  3223. });
  3224. // Empêcher la propagation des touches de texte vers la page web
  3225. // mais permettre l'interaction avec l'input
  3226. input.addEventListener("keydown", (e) => {
  3227. // Ne pas arrêter la propagation des touches de navigation (flèches, tab, etc.)
  3228. // qui sont nécessaires pour naviguer dans le champ de texte
  3229. e.stopPropagation();
  3230. });
  3231. input.addEventListener("keyup", (e) => {
  3232. e.stopPropagation();
  3233. });
  3234. input.addEventListener("keypress", (e) => {
  3235. e.stopPropagation();
  3236. });
  3237. this.blockMousePropagation(input);
  3238. return input;
  3239. }
  3240. createInfoElement(option) {
  3241. const info = document.createElement("div");
  3242. info.textContent = String(option.value);
  3243. Object.assign(info.style, {
  3244. color: "#b0b0b0",
  3245. fontSize: "12px",
  3246. fontStyle: "italic",
  3247. marginTop: "2px",
  3248. marginLeft: "6px",
  3249. marginBottom: "2px",
  3250. flex: "1 1 100%",
  3251. whiteSpace: "pre-line"
  3252. });
  3253. this.blockMousePropagation(info);
  3254. return info;
  3255. }
  3256. // Crée un bouton pour ouvrir un sous-menu de configuration de mode
  3257. createSubButton(option) {
  3258. const btn = document.createElement("button");
  3259. const isMobile = this.kxsClient.isMobile && this.kxsClient.isMobile();
  3260. // Styles pour le bouton de sous-menu (gris par défaut)
  3261. Object.assign(btn.style, {
  3262. width: "100%",
  3263. padding: isMobile ? "2px 0px" : "8px",
  3264. height: isMobile ? "24px" : "auto",
  3265. background: "#6B7280", // Couleur grise par défaut
  3266. border: "none",
  3267. borderRadius: isMobile ? "3px" : "6px",
  3268. color: "white",
  3269. cursor: "pointer",
  3270. transition: "background 0.2s",
  3271. fontSize: isMobile ? "9px" : "14px",
  3272. fontWeight: "bold",
  3273. minHeight: isMobile ? "20px" : "unset",
  3274. letterSpacing: isMobile ? "0.5px" : "1px"
  3275. });
  3276. btn.textContent = "CONFIGURE";
  3277. // Variables pour le sous-menu
  3278. let subMenuContainer = null;
  3279. // Sauvegarde des éléments originaux à masquer/afficher
  3280. let originalElements = [];
  3281. let isSubMenuOpen = false;
  3282. // Gestionnaire d'événement pour ouvrir le sous-menu
  3283. btn.addEventListener("click", () => {
  3284. // Si aucun champ n'est défini, ne rien faire
  3285. if (!option.fields || option.fields.length === 0) {
  3286. if (option.onChange) {
  3287. option.onChange(option.value);
  3288. }
  3289. return;
  3290. }
  3291. // Si le sous-menu est déjà ouvert, le fermer
  3292. if (isSubMenuOpen) {
  3293. this.closeSubMenu();
  3294. return;
  3295. }
  3296. // Trouver tous les éléments principaux à masquer
  3297. originalElements = [];
  3298. const allSections = document.querySelectorAll('.menu-section');
  3299. allSections.forEach(section => {
  3300. originalElements.push(section);
  3301. section.style.display = 'none';
  3302. });
  3303. // Masquer aussi le conteneur de la grille
  3304. const grid = document.getElementById('kxsMenuGrid');
  3305. if (grid) {
  3306. originalElements.push(grid);
  3307. grid.style.display = 'none';
  3308. }
  3309. // Créer le conteneur du sous-menu
  3310. subMenuContainer = document.createElement("div");
  3311. subMenuContainer.id = "kxs-submenu";
  3312. subMenuContainer.className = "kxs-submenu-container";
  3313. Object.assign(subMenuContainer.style, {
  3314. width: "100%",
  3315. padding: "10px 0",
  3316. boxSizing: "border-box",
  3317. overflowY: "auto",
  3318. background: "rgba(17, 24, 39, 0.95)"
  3319. });
  3320. this.blockMousePropagation(subMenuContainer);
  3321. // Créer l'en-tête du sous-menu
  3322. const subMenuHeader = document.createElement("div");
  3323. Object.assign(subMenuHeader.style, {
  3324. display: "flex",
  3325. justifyContent: "space-between",
  3326. alignItems: "center",
  3327. marginBottom: isMobile ? "10px" : "15px",
  3328. paddingBottom: isMobile ? "5px" : "10px",
  3329. borderBottom: "1px solid rgba(255, 255, 255, 0.1)",
  3330. paddingLeft: isMobile ? "10px" : "15px",
  3331. paddingRight: isMobile ? "10px" : "15px",
  3332. width: "100%",
  3333. boxSizing: "border-box"
  3334. });
  3335. this.blockMousePropagation(subMenuHeader);
  3336. // Bouton de retour
  3337. const backBtn = document.createElement("button");
  3338. backBtn.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  3339. <path d="M15 19L8 12L15 5" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  3340. </svg> Back`;
  3341. Object.assign(backBtn.style, {
  3342. background: "none",
  3343. border: "none",
  3344. color: "#fff",
  3345. cursor: "pointer",
  3346. display: "flex",
  3347. alignItems: "center",
  3348. gap: "6px",
  3349. padding: "5px",
  3350. fontSize: isMobile ? "12px" : "14px"
  3351. });
  3352. this.blockMousePropagation(backBtn);
  3353. // Titre du sous-menu
  3354. const subMenuTitle = document.createElement("h3");
  3355. subMenuTitle.textContent = option.label;
  3356. Object.assign(subMenuTitle.style, {
  3357. margin: "0",
  3358. color: "#fff",
  3359. fontSize: isMobile ? "16px" : "20px",
  3360. fontWeight: "bold",
  3361. textAlign: "center",
  3362. flex: "1"
  3363. });
  3364. this.blockMousePropagation(subMenuTitle);
  3365. // Ajouter l'événement au bouton retour pour fermer le sous-menu
  3366. backBtn.addEventListener("click", () => {
  3367. this.closeSubMenu();
  3368. });
  3369. // Assembler l'en-tête
  3370. subMenuHeader.appendChild(backBtn);
  3371. subMenuHeader.appendChild(subMenuTitle);
  3372. subMenuHeader.appendChild(document.createElement("div")); // Espace vide pour l'équilibre
  3373. subMenuContainer.appendChild(subMenuHeader);
  3374. // Créer la grille pour les options
  3375. const optionsGrid = document.createElement("div");
  3376. Object.assign(optionsGrid.style, {
  3377. display: "grid",
  3378. gridTemplateColumns: isMobile ? "repeat(2, 1fr)" : "repeat(3, 1fr)",
  3379. gap: isMobile ? "8px" : "16px",
  3380. padding: isMobile ? "4px" : "16px",
  3381. gridAutoRows: isMobile ? "minmax(100px, auto)" : "minmax(150px, auto)",
  3382. width: "100%",
  3383. boxSizing: "border-box"
  3384. });
  3385. this.blockMousePropagation(optionsGrid);
  3386. // Créer les cartes pour chaque option
  3387. option.fields.forEach(mod => {
  3388. this.createModCard(mod, optionsGrid);
  3389. });
  3390. subMenuContainer.appendChild(optionsGrid);
  3391. // Ajouter le sous-menu au menu principal
  3392. this.menu.appendChild(subMenuContainer);
  3393. isSubMenuOpen = true;
  3394. // Définir la méthode pour fermer le sous-menu
  3395. this.closeSubMenu = () => {
  3396. // Supprimer le sous-menu
  3397. if (subMenuContainer && subMenuContainer.parentElement) {
  3398. subMenuContainer.parentElement.removeChild(subMenuContainer);
  3399. }
  3400. // Réafficher tous les éléments originaux
  3401. originalElements.forEach(el => {
  3402. if (el.id === 'kxsMenuGrid') {
  3403. el.style.display = 'grid';
  3404. }
  3405. else {
  3406. el.style.display = 'block';
  3407. }
  3408. });
  3409. // Réinitialiser les états
  3410. this.filterOptions(); // S'assurer que les options sont correctement filtrées
  3411. subMenuContainer = null;
  3412. isSubMenuOpen = false;
  3413. };
  3414. // Appeler le callback si défini
  3415. if (option.onChange) {
  3416. option.onChange(option.value);
  3417. }
  3418. });
  3419. this.blockMousePropagation(btn);
  3420. return btn;
  3421. }
  3422. // Crée une carte pour un mod dans le sous-menu
  3423. createModCard(mod, container) {
  3424. const modCard = document.createElement("div");
  3425. const isMobile = this.kxsClient.isMobile && this.kxsClient.isMobile();
  3426. Object.assign(modCard.style, {
  3427. background: "rgba(31, 41, 55, 0.8)",
  3428. borderRadius: "10px",
  3429. padding: isMobile ? "10px" : "16px",
  3430. display: "flex",
  3431. flexDirection: "column",
  3432. alignItems: "center",
  3433. gap: isMobile ? "8px" : "12px",
  3434. minHeight: isMobile ? "100px" : "150px",
  3435. });
  3436. // Icône
  3437. const iconContainer = document.createElement("div");
  3438. Object.assign(iconContainer.style, {
  3439. width: isMobile ? "32px" : "48px",
  3440. height: isMobile ? "32px" : "48px",
  3441. borderRadius: "50%",
  3442. display: "flex",
  3443. alignItems: "center",
  3444. justifyContent: "center",
  3445. marginBottom: isMobile ? "4px" : "8px"
  3446. });
  3447. iconContainer.innerHTML = mod.icon || '';
  3448. // Titre
  3449. const title = document.createElement("div");
  3450. title.textContent = mod.label;
  3451. title.style.fontSize = isMobile ? "14px" : "16px";
  3452. title.style.textAlign = "center";
  3453. // Contrôle selon le type
  3454. let control = null;
  3455. switch (mod.type) {
  3456. case "info":
  3457. control = this.createInfoElement(mod);
  3458. break;
  3459. case "input":
  3460. control = this.createInputElement(mod);
  3461. break;
  3462. case "toggle":
  3463. control = this.createToggleButton(mod);
  3464. break;
  3465. case "click":
  3466. control = this.createClickButton(mod);
  3467. }
  3468. modCard.appendChild(iconContainer);
  3469. modCard.appendChild(title);
  3470. if (control) {
  3471. modCard.appendChild(control);
  3472. }
  3473. container.appendChild(modCard);
  3474. this.blockMousePropagation(modCard);
  3475. }
  3476. addDragListeners() {
  3477. this.menu.addEventListener('mousedown', (e) => {
  3478. // Ne pas arrêter la propagation si l'événement vient d'un élément interactif
  3479. if (e.target instanceof HTMLElement &&
  3480. e.target.matches("input, select, button, svg, path")) {
  3481. // Laisser l'événement se propager aux éléments interactifs
  3482. return;
  3483. }
  3484. // Empêcher la propagation de l'événement mousedown vers la page web
  3485. e.stopPropagation();
  3486. // Activer le drag & drop seulement si on clique sur une zone non interactive
  3487. if (e.target instanceof HTMLElement &&
  3488. !e.target.matches("input, select, button, svg, path")) {
  3489. this.isDragging = true;
  3490. const rect = this.menu.getBoundingClientRect();
  3491. this.dragOffset = {
  3492. x: e.clientX - rect.left,
  3493. y: e.clientY - rect.top
  3494. };
  3495. this.menu.style.cursor = "grabbing";
  3496. }
  3497. });
  3498. document.addEventListener('mousemove', (e) => {
  3499. if (this.isDragging) {
  3500. const x = e.clientX - this.dragOffset.x;
  3501. const y = e.clientY - this.dragOffset.y;
  3502. this.menu.style.transform = 'none';
  3503. this.menu.style.left = `${x}px`;
  3504. this.menu.style.top = `${y}px`;
  3505. }
  3506. });
  3507. document.addEventListener('mouseup', (e) => {
  3508. // Arrêter le drag & drop
  3509. const wasDragging = this.isDragging;
  3510. this.isDragging = false;
  3511. this.menu.style.cursor = "grab";
  3512. // Empêcher la propagation de l'événement mouseup vers la page web
  3513. // seulement si l'événement vient du menu et n'est pas un élément interactif
  3514. if (this.menu.contains(e.target)) {
  3515. if (wasDragging || !(e.target instanceof HTMLElement && e.target.matches("input, select, button, svg, path"))) {
  3516. e.stopPropagation();
  3517. }
  3518. }
  3519. });
  3520. }
  3521. toggleMenuVisibility() {
  3522. this.isClientMenuVisible = !this.isClientMenuVisible;
  3523. // Mettre à jour la propriété publique en même temps
  3524. this.isOpen = this.isClientMenuVisible;
  3525. if (this.kxsClient.isNotifyingForToggleMenu) {
  3526. this.kxsClient.nm.showNotification(this.isClientMenuVisible ? "Opening menu..." : "Closing menu...", "info", 1400);
  3527. }
  3528. this.menu.style.display = this.isClientMenuVisible ? "block" : "none";
  3529. // If opening the menu, make sure to display options
  3530. if (this.isClientMenuVisible) {
  3531. this.filterOptions();
  3532. }
  3533. }
  3534. destroy() {
  3535. // Remove global event listeners
  3536. window.removeEventListener("keydown", this.shiftListener);
  3537. document.removeEventListener('mousemove', this.mouseMoveListener);
  3538. document.removeEventListener('mouseup', this.mouseUpListener);
  3539. // Supprimer tous les écouteurs d'événements keydown du document
  3540. // Nous ne pouvons pas supprimer directement l'écouteur anonyme, mais ce n'est pas grave
  3541. // car la vérification isClientMenuVisible empêchera toute action une fois le menu détruit
  3542. // Remove all event listeners from menu elements
  3543. const removeAllListeners = (element) => {
  3544. var _a;
  3545. const clone = element.cloneNode(true);
  3546. (_a = element.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(clone, element);
  3547. };
  3548. // Clean up all buttons and inputs in the menu
  3549. this.menu.querySelectorAll('button, input').forEach(element => {
  3550. removeAllListeners(element);
  3551. });
  3552. // Remove the menu from DOM
  3553. this.menu.remove();
  3554. // Clear all sections
  3555. this.sections.forEach(section => {
  3556. if (section.element) {
  3557. removeAllListeners(section.element);
  3558. section.element.remove();
  3559. delete section.element;
  3560. }
  3561. section.options = [];
  3562. });
  3563. this.sections = [];
  3564. // Reset all class properties
  3565. this.isClientMenuVisible = false;
  3566. this.isDragging = false;
  3567. this.dragOffset = { x: 0, y: 0 };
  3568. this.activeCategory = "ALL";
  3569. // Clear references
  3570. this.menu = null;
  3571. this.kxsClient = null;
  3572. }
  3573. getMenuVisibility() {
  3574. return this.isClientMenuVisible;
  3575. }
  3576. }
  3577.  
  3578.  
  3579. ;// ./src/SERVER/Ping.ts
  3580. class PingTest {
  3581. constructor() {
  3582. this.ping = 0;
  3583. this.ws = null;
  3584. this.sendTime = 0;
  3585. this.retryCount = 0;
  3586. this.isConnecting = false;
  3587. this.isWebSocket = true;
  3588. this.url = "";
  3589. this.region = "";
  3590. this.hasPing = false;
  3591. this.reconnectTimer = null;
  3592. this.keepAliveTimer = null;
  3593. this.connectionCheckTimer = null;
  3594. this.ptcDataBuf = new ArrayBuffer(1);
  3595. this.waitForServerSelectElements();
  3596. this.startKeepAlive();
  3597. }
  3598. startKeepAlive() {
  3599. // Annuler l'ancien timer si existant
  3600. if (this.keepAliveTimer) {
  3601. clearInterval(this.keepAliveTimer);
  3602. }
  3603. this.keepAliveTimer = setInterval(() => {
  3604. var _a, _b, _c;
  3605. if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
  3606. this.ws.send(this.ptcDataBuf);
  3607. }
  3608. else if (((_b = this.ws) === null || _b === void 0 ? void 0 : _b.readyState) === WebSocket.CLOSED || ((_c = this.ws) === null || _c === void 0 ? void 0 : _c.readyState) === WebSocket.CLOSING) {
  3609. // Redémarrer la connexion si elle est fermée
  3610. this.restart();
  3611. }
  3612. }, 5000); // envoie toutes les 5s
  3613. }
  3614. waitForServerSelectElements() {
  3615. const checkInterval = setInterval(() => {
  3616. const teamSelect = document.getElementById("team-server-select");
  3617. const mainSelect = document.getElementById("server-select-main");
  3618. const selectedValue = (teamSelect === null || teamSelect === void 0 ? void 0 : teamSelect.value) || (mainSelect === null || mainSelect === void 0 ? void 0 : mainSelect.value);
  3619. if ((teamSelect || mainSelect) && selectedValue) {
  3620. clearInterval(checkInterval);
  3621. this.setServerFromDOM();
  3622. this.attachRegionChangeListener();
  3623. }
  3624. }, 100); // Vérifie toutes les 100ms
  3625. }
  3626. setServerFromDOM() {
  3627. const { region, url } = this.detectSelectedServer();
  3628. this.region = region;
  3629. this.url = `wss://${url}/ptc`;
  3630. this.start();
  3631. }
  3632. detectSelectedServer() {
  3633. const currentUrl = window.location.href;
  3634. const isSpecialUrl = /\/#\w+/.test(currentUrl);
  3635. const teamSelectElement = document.getElementById("team-server-select");
  3636. const mainSelectElement = document.getElementById("server-select-main");
  3637. const region = isSpecialUrl && teamSelectElement
  3638. ? teamSelectElement.value
  3639. : (mainSelectElement === null || mainSelectElement === void 0 ? void 0 : mainSelectElement.value) || "NA";
  3640. const servers = [
  3641. { region: "NA", url: "usr.mathsiscoolfun.com:8001" },
  3642. { region: "EU", url: "eur.mathsiscoolfun.com:8001" },
  3643. { region: "Asia", url: "asr.mathsiscoolfun.com:8001" },
  3644. { region: "SA", url: "sa.mathsiscoolfun.com:8001" },
  3645. ];
  3646. const selectedServer = servers.find((s) => s.region.toUpperCase() === region.toUpperCase());
  3647. if (!selectedServer)
  3648. throw new Error("Aucun serveur correspondant trouvé");
  3649. return selectedServer;
  3650. }
  3651. attachRegionChangeListener() {
  3652. const teamSelectElement = document.getElementById("team-server-select");
  3653. const mainSelectElement = document.getElementById("server-select-main");
  3654. const onChange = () => {
  3655. const { region } = this.detectSelectedServer();
  3656. if (region !== this.region) {
  3657. this.restart();
  3658. }
  3659. };
  3660. teamSelectElement === null || teamSelectElement === void 0 ? void 0 : teamSelectElement.addEventListener("change", onChange);
  3661. mainSelectElement === null || mainSelectElement === void 0 ? void 0 : mainSelectElement.addEventListener("change", onChange);
  3662. }
  3663. start() {
  3664. if (this.isConnecting)
  3665. return;
  3666. this.isConnecting = true;
  3667. this.startWebSocketPing();
  3668. // Vérifier régulièrement l'état de la connexion
  3669. this.startConnectionCheck();
  3670. }
  3671. startConnectionCheck() {
  3672. // Annuler l'ancien timer si existant
  3673. if (this.connectionCheckTimer) {
  3674. clearInterval(this.connectionCheckTimer);
  3675. }
  3676. // Vérifier l'état de la connexion toutes les 10 secondes
  3677. this.connectionCheckTimer = setInterval(() => {
  3678. // Si on n'a pas de ping valide ou que la connexion est fermée, on tente de reconnecter
  3679. if (!this.hasPing || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
  3680. this.restart();
  3681. }
  3682. }, 10000);
  3683. }
  3684. startWebSocketPing() {
  3685. if (this.ws || !this.url)
  3686. return;
  3687. const ws = new WebSocket(this.url);
  3688. ws.binaryType = "arraybuffer";
  3689. ws.onopen = () => {
  3690. this.ws = ws;
  3691. this.retryCount = 0;
  3692. this.isConnecting = false;
  3693. this.sendPing();
  3694. setTimeout(() => {
  3695. var _a;
  3696. if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) !== WebSocket.OPEN) {
  3697. this.restart();
  3698. }
  3699. }, 3000); // 3s pour sécuriser
  3700. };
  3701. ws.onmessage = () => {
  3702. this.hasPing = true;
  3703. const elapsed = (Date.now() - this.sendTime) / 1e3;
  3704. this.ping = Math.round(elapsed * 1000);
  3705. setTimeout(() => this.sendPing(), 1000);
  3706. };
  3707. ws.onerror = (error) => {
  3708. this.ping = 0;
  3709. this.hasPing = false;
  3710. this.retryCount++;
  3711. // Tentative immédiate mais avec backoff exponentiel
  3712. const retryDelay = Math.min(1000 * Math.pow(2, this.retryCount - 1), 10000);
  3713. // Annuler tout timer de reconnexion existant
  3714. if (this.reconnectTimer) {
  3715. clearTimeout(this.reconnectTimer);
  3716. }
  3717. this.reconnectTimer = setTimeout(() => {
  3718. this.ws = null; // S'assurer que l'ancienne connexion est effacée
  3719. this.startWebSocketPing();
  3720. }, retryDelay);
  3721. };
  3722. ws.onclose = (event) => {
  3723. this.hasPing = false;
  3724. this.ws = null;
  3725. this.isConnecting = false;
  3726. // Tentative de reconnexion après une fermeture
  3727. if (this.reconnectTimer) {
  3728. clearTimeout(this.reconnectTimer);
  3729. }
  3730. this.reconnectTimer = setTimeout(() => {
  3731. this.start();
  3732. }, 2000); // Attendre 2 secondes avant de reconnecter
  3733. };
  3734. }
  3735. sendPing() {
  3736. var _a, _b;
  3737. if (this.ws && this.ws.readyState === WebSocket.OPEN) {
  3738. this.sendTime = Date.now();
  3739. this.ws.send(this.ptcDataBuf);
  3740. }
  3741. else if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.CLOSED || ((_b = this.ws) === null || _b === void 0 ? void 0 : _b.readyState) === WebSocket.CLOSING) {
  3742. // Si la WebSocket est fermée au moment d'envoyer le ping, on tente de reconnecter
  3743. this.restart();
  3744. }
  3745. }
  3746. stop() {
  3747. // Annuler tous les timers
  3748. if (this.reconnectTimer) {
  3749. clearTimeout(this.reconnectTimer);
  3750. this.reconnectTimer = null;
  3751. }
  3752. if (this.keepAliveTimer) {
  3753. clearInterval(this.keepAliveTimer);
  3754. this.keepAliveTimer = null;
  3755. }
  3756. if (this.connectionCheckTimer) {
  3757. clearInterval(this.connectionCheckTimer);
  3758. this.connectionCheckTimer = null;
  3759. }
  3760. if (this.ws) {
  3761. this.ws.onclose = null;
  3762. this.ws.onerror = null;
  3763. this.ws.onmessage = null;
  3764. this.ws.onopen = null;
  3765. this.ws.close();
  3766. this.ws = null;
  3767. }
  3768. this.isConnecting = false;
  3769. this.retryCount = 0;
  3770. this.hasPing = false;
  3771. }
  3772. restart() {
  3773. this.stop();
  3774. setTimeout(() => {
  3775. this.setServerFromDOM();
  3776. }, 500); // Petit délai pour éviter les problèmes de rebond
  3777. }
  3778. /**
  3779. * Retourne le ping actuel. Ne touche jamais à la websocket ici !
  3780. * Si le ping n'est pas dispo, retourne -1 (jamais null).
  3781. * La reconnexion doit être gérée ailleurs (timer, event, etc).
  3782. */
  3783. getPingResult() {
  3784. if (this.ws && this.ws.readyState === WebSocket.OPEN && this.hasPing) {
  3785. return {
  3786. region: this.region,
  3787. ping: this.ping,
  3788. };
  3789. }
  3790. else {
  3791. // Si on détecte un problème ici, planifier une reconnexion
  3792. if (!this.reconnectTimer && (!this.ws || this.ws.readyState !== WebSocket.CONNECTING)) {
  3793. this.reconnectTimer = setTimeout(() => this.restart(), 1000);
  3794. }
  3795. return {
  3796. region: this.region,
  3797. ping: -1, // -1 indique que le ping n'est pas dispo, mais jamais null
  3798. };
  3799. }
  3800. }
  3801. }
  3802.  
  3803.  
  3804. ;// ./src/HUD/ClientHUD.ts
  3805.  
  3806. class KxsClientHUD {
  3807. constructor(kxsClient) {
  3808. this.healthAnimations = [];
  3809. this.lastHealthValue = 100;
  3810. this.hudOpacityObservers = [];
  3811. this.weaponBorderObservers = [];
  3812. this.ctrlFocusTimer = null;
  3813. this.killFeedObserver = null;
  3814. this.kxsClient = kxsClient;
  3815. this.frameCount = 0;
  3816. this.fps = 0;
  3817. this.kills = 0;
  3818. this.isMenuVisible = true;
  3819. this.pingManager = new PingTest();
  3820. this.allDivToHide = [
  3821. '#ui-medical-interactive > div',
  3822. '#ui-ammo-interactive > div',
  3823. '#ui-weapon-container .ui-weapon-switch',
  3824. '#ui-killfeed',
  3825. '#ui-killfeed-contents',
  3826. '.killfeed-div',
  3827. '.killfeed-text',
  3828. '#ui-kill-leader-container',
  3829. '#ui-kill-leader-wrapper',
  3830. '#ui-kill-leader-name',
  3831. '#ui-kill-leader-icon',
  3832. '#ui-kill-leader-count',
  3833. '#ui-leaderboard-wrapper',
  3834. '#ui-leaderboard',
  3835. '#ui-leaderboard-alive',
  3836. '#ui-leaderboard-alive-faction',
  3837. '.ui-leaderboard-header',
  3838. '#ui-kill-counter-wrapper',
  3839. '#ui-kill-counter',
  3840. '.ui-player-kills',
  3841. '.ui-kill-counter-header',
  3842. '#ui-bottom-center-right',
  3843. '#ui-armor-helmet',
  3844. '#ui-armor-chest',
  3845. '#ui-armor-backpack',
  3846. '.ui-armor-counter',
  3847. '.ui-armor-counter-inner',
  3848. '.ui-armor-level',
  3849. '.ui-armor-image',
  3850. '.ui-loot-image',
  3851. ];
  3852. if (this.kxsClient.isPingVisible) {
  3853. this.initCounter("ping", "Ping", "45ms");
  3854. }
  3855. if (this.kxsClient.isFpsVisible) {
  3856. this.initCounter("fps", "FPS", "60");
  3857. }
  3858. if (this.kxsClient.isKillsVisible) {
  3859. this.initCounter("kills", "Kills", "0");
  3860. }
  3861. if (this.kxsClient.isGunOverlayColored) {
  3862. this.toggleWeaponBorderHandler();
  3863. }
  3864. this.startUpdateLoop();
  3865. this.escapeMenu();
  3866. this.initFriendDetector();
  3867. if (this.kxsClient.isKillFeedBlint) {
  3868. if (document.readyState === 'loading') {
  3869. document.addEventListener('DOMContentLoaded', this.initKillFeed);
  3870. }
  3871. else {
  3872. this.initKillFeed();
  3873. }
  3874. }
  3875. if (this.kxsClient.customCrosshair !== null) {
  3876. this.loadCustomCrosshair();
  3877. }
  3878. this.setupCtrlFocusModeListener();
  3879. }
  3880. setupCtrlFocusModeListener() {
  3881. document.addEventListener('keydown', (e) => {
  3882. if (e.code === 'ControlLeft' && !this.ctrlFocusTimer) {
  3883. this.ctrlFocusTimer = window.setTimeout(() => {
  3884. this.kxsClient.isFocusModeEnabled = !this.kxsClient.isFocusModeEnabled;
  3885. this.kxsClient.hud.toggleFocusMode();
  3886. this.kxsClient.nm.showNotification("Focus mode toggled", "info", 1200);
  3887. }, 1000);
  3888. }
  3889. });
  3890. document.addEventListener('keyup', (e) => {
  3891. if (e.code === 'ControlLeft' && this.ctrlFocusTimer) {
  3892. clearTimeout(this.ctrlFocusTimer);
  3893. this.ctrlFocusTimer = null;
  3894. }
  3895. });
  3896. }
  3897. initFriendDetector() {
  3898. // Initialize friends list
  3899. let all_friends = this.kxsClient.all_friends.split(',') || [];
  3900. if (all_friends.length >= 1) {
  3901. // Create a cache for detected friends
  3902. // Structure will be: { "friendName": timestamp }
  3903. const friendsCache = {};
  3904. // Cache duration in milliseconds (4 minutes = 240000 ms)
  3905. const cacheDuration = 4 * 60 * 1000;
  3906. // Select the element containing kill feeds
  3907. const killfeedContents = document.querySelector('#ui-killfeed-contents');
  3908. if (killfeedContents) {
  3909. // Keep track of last seen content for each div
  3910. const lastSeenContent = {
  3911. "ui-killfeed-0": "",
  3912. "ui-killfeed-1": "",
  3913. "ui-killfeed-2": "",
  3914. "ui-killfeed-3": "",
  3915. "ui-killfeed-4": "",
  3916. "ui-killfeed-5": ""
  3917. };
  3918. // Function to check if a friend is in the text with cache management
  3919. const checkForFriends = (text, divId) => {
  3920. // If the text is identical to the last seen, ignore
  3921. // @ts-ignore
  3922. if (text === lastSeenContent[divId])
  3923. return;
  3924. // Update the last seen content
  3925. // @ts-ignore
  3926. lastSeenContent[divId] = text;
  3927. // Ignore empty messages
  3928. if (!text.trim())
  3929. return;
  3930. // Current timestamp
  3931. const currentTime = Date.now();
  3932. // Check if a friend is mentioned
  3933. for (let friend of all_friends) {
  3934. if (friend !== "" && text.includes(friend)) {
  3935. // Check if the friend is in the cache and if the cache is still valid
  3936. // @ts-ignore
  3937. const lastSeen = friendsCache[friend];
  3938. if (!lastSeen || (currentTime - lastSeen > cacheDuration)) {
  3939. // Update the cache
  3940. // @ts-ignore
  3941. friendsCache[friend] = currentTime;
  3942. // Display notification
  3943. this.kxsClient.nm.showNotification(`[FriendDetector] ${friend} is in this game`, "info", 2300);
  3944. }
  3945. break;
  3946. }
  3947. }
  3948. };
  3949. // Function to check all kill feeds
  3950. const checkAllKillfeeds = () => {
  3951. all_friends = this.kxsClient.all_friends.split(',') || [];
  3952. for (let i = 0; i <= 5; i++) {
  3953. const divId = `ui-killfeed-${i}`;
  3954. const killDiv = document.getElementById(divId);
  3955. if (killDiv) {
  3956. const textElement = killDiv.querySelector('.killfeed-text');
  3957. if (textElement && textElement.textContent) {
  3958. checkForFriends(textElement.textContent, divId);
  3959. }
  3960. }
  3961. }
  3962. };
  3963. // Observe style or text changes in the entire container
  3964. const observer = new MutationObserver(() => {
  3965. checkAllKillfeeds();
  3966. });
  3967. // Start observing with a configuration that detects all changes
  3968. observer.observe(killfeedContents, {
  3969. childList: true, // Observe changes to child elements
  3970. subtree: true, // Observe the entire tree
  3971. characterData: true, // Observe text changes
  3972. attributes: true // Observe attribute changes (like style/opacity)
  3973. });
  3974. // Check current content immediately
  3975. checkAllKillfeeds();
  3976. }
  3977. else {
  3978. this.kxsClient.logger.error("Killfeed-contents element not found");
  3979. }
  3980. }
  3981. }
  3982. initKillFeed() {
  3983. this.applyCustomStyles();
  3984. this.setupObserver();
  3985. }
  3986. toggleKillFeed() {
  3987. if (this.kxsClient.isKillFeedBlint) {
  3988. this.initKillFeed(); // <-- injecte le CSS custom et observer
  3989. }
  3990. else {
  3991. this.resetKillFeed(); // <-- supprime styles et contenu
  3992. }
  3993. }
  3994. /**
  3995. * Réinitialise le Kill Feed à l'état par défaut (vide)
  3996. */
  3997. /**
  3998. * Supprime tous les styles custom KillFeed injectés par applyCustomStyles
  3999. */
  4000. resetKillFeedStyles() {
  4001. // Supprime tous les <style> contenant .killfeed-div ou .killfeed-text
  4002. const styles = Array.from(document.head.querySelectorAll('style'));
  4003. styles.forEach(style => {
  4004. if (style.textContent &&
  4005. (style.textContent.includes('.killfeed-div') || style.textContent.includes('.killfeed-text'))) {
  4006. style.remove();
  4007. }
  4008. });
  4009. }
  4010. observeHudOpacity(opacity) {
  4011. // Nettoie d'abord les observers existants
  4012. this.hudOpacityObservers.forEach(obs => obs.disconnect());
  4013. this.hudOpacityObservers = [];
  4014. this.allDivToHide.forEach(sel => {
  4015. const elements = document.querySelectorAll(sel);
  4016. elements.forEach(el => {
  4017. el.style.opacity = String(opacity);
  4018. // Applique aussi l'opacité à tous les descendants
  4019. const descendants = el.querySelectorAll('*');
  4020. descendants.forEach(child => {
  4021. child.style.opacity = String(opacity);
  4022. });
  4023. // Observer pour le parent
  4024. const observer = new MutationObserver(mutations => {
  4025. mutations.forEach(mutation => {
  4026. if (mutation.type === "attributes" &&
  4027. mutation.attributeName === "style") {
  4028. const currentOpacity = el.style.opacity;
  4029. if (currentOpacity !== String(opacity)) {
  4030. el.style.opacity = String(opacity);
  4031. }
  4032. // Vérifie aussi les enfants
  4033. const descendants = el.querySelectorAll('*');
  4034. descendants.forEach(child => {
  4035. if (child.style.opacity !== String(opacity)) {
  4036. child.style.opacity = String(opacity);
  4037. }
  4038. });
  4039. }
  4040. });
  4041. });
  4042. observer.observe(el, { attributes: true, attributeFilter: ["style"] });
  4043. this.hudOpacityObservers.push(observer);
  4044. // Observer pour chaque enfant (optionnel mais robuste)
  4045. descendants.forEach(child => {
  4046. const childObserver = new MutationObserver(mutations => {
  4047. mutations.forEach(mutation => {
  4048. if (mutation.type === "attributes" &&
  4049. mutation.attributeName === "style") {
  4050. if (child.style.opacity !== String(opacity)) {
  4051. child.style.opacity = String(opacity);
  4052. }
  4053. }
  4054. });
  4055. });
  4056. childObserver.observe(child, { attributes: true, attributeFilter: ["style"] });
  4057. this.hudOpacityObservers.push(childObserver);
  4058. });
  4059. });
  4060. });
  4061. }
  4062. toggleFocusMode() {
  4063. if (this.kxsClient.isFocusModeEnabled) {
  4064. this.observeHudOpacity(0.05);
  4065. }
  4066. else {
  4067. // 1. Stoppe tous les observers
  4068. this.hudOpacityObservers.forEach(obs => obs.disconnect());
  4069. this.hudOpacityObservers = [];
  4070. this.allDivToHide.forEach(sel => {
  4071. const elements = document.querySelectorAll(sel);
  4072. elements.forEach(el => {
  4073. el.style.removeProperty('opacity');
  4074. // Supprime aussi sur tous les enfants
  4075. const descendants = el.querySelectorAll('*');
  4076. descendants.forEach(child => {
  4077. child.style.removeProperty('opacity');
  4078. });
  4079. });
  4080. });
  4081. }
  4082. }
  4083. resetKillFeed() {
  4084. // Supprime les styles custom KillFeed
  4085. this.resetKillFeedStyles();
  4086. // Sélectionne le container du killfeed
  4087. const killfeedContents = document.getElementById('ui-killfeed-contents');
  4088. if (killfeedContents) {
  4089. // Vide tous les killfeed-div et killfeed-text
  4090. killfeedContents.querySelectorAll('.killfeed-div').forEach(div => {
  4091. const text = div.querySelector('.killfeed-text');
  4092. if (text)
  4093. text.textContent = '';
  4094. div.style.opacity = '0';
  4095. });
  4096. }
  4097. }
  4098. loadCustomCrosshair() {
  4099. const url = this.kxsClient.customCrosshair;
  4100. // Supprime l'ancienne règle si elle existe
  4101. const styleId = 'kxs-custom-cursor-style';
  4102. const oldStyle = document.getElementById(styleId);
  4103. if (oldStyle)
  4104. oldStyle.remove();
  4105. // Débranche l'ancien observer s'il existe
  4106. if (this.customCursorObserver) {
  4107. this.customCursorObserver.disconnect();
  4108. this.customCursorObserver = undefined;
  4109. }
  4110. // Réinitialise le curseur si pas d'URL
  4111. if (!url) {
  4112. // Supprime l'image animée si présente
  4113. if (this.animatedCursorImg) {
  4114. this.animatedCursorImg.remove();
  4115. this.animatedCursorImg = undefined;
  4116. }
  4117. // Supprime le style CSS qui cache le curseur natif
  4118. const hideCursorStyle = document.getElementById('kxs-hide-cursor-style');
  4119. if (hideCursorStyle)
  4120. hideCursorStyle.remove();
  4121. // Supprime le style CSS du curseur personnalisé
  4122. const customCursorStyle = document.getElementById('kxs-custom-cursor-style');
  4123. if (customCursorStyle)
  4124. customCursorStyle.remove();
  4125. // Retire l'eventListener mousemove si défini
  4126. if (this._mousemoveHandler) {
  4127. document.removeEventListener('mousemove', this._mousemoveHandler);
  4128. this._mousemoveHandler = undefined;
  4129. }
  4130. document.body.style.cursor = '';
  4131. return;
  4132. }
  4133. // Curseur animé JS : gestion d'un GIF
  4134. const isGif = url.split('?')[0].toLowerCase().endsWith('.gif');
  4135. // Nettoyage si on repasse sur un non-GIF
  4136. if (this.animatedCursorImg) {
  4137. this.animatedCursorImg.remove();
  4138. this.animatedCursorImg = undefined;
  4139. }
  4140. if (this._mousemoveHandler) {
  4141. document.removeEventListener('mousemove', this._mousemoveHandler);
  4142. this._mousemoveHandler = undefined;
  4143. }
  4144. if (isGif) {
  4145. // Ajoute une règle CSS globale pour cacher le curseur natif partout
  4146. let hideCursorStyle = document.getElementById('kxs-hide-cursor-style');
  4147. if (!hideCursorStyle) {
  4148. hideCursorStyle = document.createElement('style');
  4149. hideCursorStyle.id = 'kxs-hide-cursor-style';
  4150. hideCursorStyle.innerHTML = `
  4151. * { cursor: none !important; }
  4152. *:hover { cursor: none !important; }
  4153. *:active { cursor: none !important; }
  4154. *:focus { cursor: none !important; }
  4155. input, textarea { cursor: none !important; }
  4156. a, button, [role="button"], [onclick] { cursor: none !important; }
  4157. [draggable="true"] { cursor: none !important; }
  4158. [style*="cursor: pointer"] { cursor: none !important; }
  4159. [style*="cursor: text"] { cursor: none !important; }
  4160. [style*="cursor: move"] { cursor: none !important; }
  4161. [style*="cursor: crosshair"] { cursor: none !important; }
  4162. [style*="cursor: ew-resize"] { cursor: none !important; }
  4163. [style*="cursor: ns-resize"] { cursor: none !important; }
  4164. `;
  4165. document.head.appendChild(hideCursorStyle);
  4166. }
  4167. const animatedImg = document.createElement('img');
  4168. animatedImg.src = url;
  4169. animatedImg.style.position = 'fixed';
  4170. animatedImg.style.pointerEvents = 'none';
  4171. animatedImg.style.zIndex = '99999';
  4172. animatedImg.style.width = '38px';
  4173. animatedImg.style.height = '38px';
  4174. animatedImg.style.left = '0px';
  4175. animatedImg.style.top = '0px';
  4176. this.animatedCursorImg = animatedImg;
  4177. document.body.appendChild(animatedImg);
  4178. this._mousemoveHandler = (e) => {
  4179. if (this.animatedCursorImg) {
  4180. this.animatedCursorImg.style.left = `${e.clientX}px`;
  4181. this.animatedCursorImg.style.top = `${e.clientY}px`;
  4182. }
  4183. };
  4184. document.addEventListener('mousemove', this._mousemoveHandler);
  4185. return;
  4186. }
  4187. // Nettoie la règle cursor:none si on repasse sur un curseur natif
  4188. const hideCursorStyle = document.getElementById('kxs-hide-cursor-style');
  4189. if (hideCursorStyle)
  4190. hideCursorStyle.remove();
  4191. // Sinon, méthode classique : précharge l'image, puis applique le curseur natif
  4192. const img = new window.Image();
  4193. img.onload = () => {
  4194. const style = document.createElement('style');
  4195. style.id = styleId;
  4196. style.innerHTML = `
  4197. * { cursor: url('${url}'), auto !important; }
  4198. *:hover { cursor: url('${url}'), pointer !important; }
  4199. *:active { cursor: url('${url}'), pointer !important; }
  4200. *:focus { cursor: url('${url}'), text !important; }
  4201. input, textarea { cursor: url('${url}'), text !important; }
  4202. a, button, [role="button"], [onclick] { cursor: url('${url}'), pointer !important; }
  4203. [draggable="true"] { cursor: url('${url}'), move !important; }
  4204. [style*="cursor: pointer"] { cursor: url('${url}'), pointer !important; }
  4205. [style*="cursor: text"] { cursor: url('${url}'), text !important; }
  4206. [style*="cursor: move"] { cursor: url('${url}'), move !important; }
  4207. [style*="cursor: crosshair"] { cursor: url('${url}'), crosshair !important; }
  4208. [style*="cursor: ew-resize"] { cursor: url('${url}'), ew-resize !important; }
  4209. [style*="cursor: ns-resize"] { cursor: url('${url}'), ns-resize !important; }
  4210. `;
  4211. document.head.appendChild(style);
  4212. };
  4213. img.onerror = () => {
  4214. document.body.style.cursor = '';
  4215. this.kxsClient.logger.warn('Impossible de charger le curseur personnalisé:', url);
  4216. };
  4217. img.src = url;
  4218. // --- MutationObserver pour forcer le curseur même si le jeu le réécrit ---
  4219. this.customCursorObserver = new MutationObserver((mutations) => {
  4220. for (const mutation of mutations) {
  4221. if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
  4222. const node = mutation.target;
  4223. if (node.style && node.style.cursor && !node.style.cursor.includes(url)) {
  4224. node.style.cursor = `url('${url}'), auto`;
  4225. }
  4226. }
  4227. }
  4228. });
  4229. // Observe tous les changements de style sur tout le body et sur #game-touch-area
  4230. const gameTouchArea = document.getElementById('game-touch-area');
  4231. if (gameTouchArea) {
  4232. this.customCursorObserver.observe(gameTouchArea, {
  4233. attributes: true,
  4234. attributeFilter: ['style'],
  4235. subtree: true
  4236. });
  4237. }
  4238. this.customCursorObserver.observe(document.body, {
  4239. attributes: true,
  4240. attributeFilter: ['style'],
  4241. subtree: true
  4242. });
  4243. }
  4244. escapeMenu() {
  4245. const customStylesMobile = `
  4246. .ui-game-menu-desktop {
  4247. background: linear-gradient(135deg, rgba(25, 25, 35, 0.95) 0%, rgba(15, 15, 25, 0.98) 100%) !important;
  4248. border: 1px solid rgba(255, 255, 255, 0.1) !important;
  4249. border-radius: 4px !important;
  4250. box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15) !important;
  4251. padding: 2px 2px !important;
  4252. max-width: 45vw !important;
  4253. width: 45vw !important;
  4254. max-height: 28vh !important;
  4255. min-width: unset !important;
  4256. min-height: unset !important;
  4257. font-size: 9px !important;
  4258. margin: 0 auto !important;
  4259. box-sizing: border-box !important;
  4260. overflow-y: auto !important;
  4261. }
  4262. .ui-game-menu-desktop button, .ui-game-menu-desktop .btn, .ui-game-menu-desktop input, .ui-game-menu-desktop select {
  4263. font-size: 9px !important;
  4264. padding: 2px 3px !important;
  4265. margin: 1px 0 !important;
  4266. border-radius: 3px !important;
  4267. }
  4268. .ui-game-menu-desktop .kxs-header, .ui-game-menu-desktop h1, .ui-game-menu-desktop h2, .ui-game-menu-desktop h3, .ui-game-menu-desktop label, .ui-game-menu-desktop span {
  4269. font-size: 9px !important;
  4270. }
  4271. .ui-game-menu-desktop img, .ui-game-menu-desktop svg {
  4272. width: 10px !important;
  4273. height: 10px !important;
  4274. }
  4275. .ui-game-menu-desktop .mode-btn {
  4276. min-height: 12px !important;
  4277. font-size: 8px !important;
  4278. padding: 2px 3px !important;
  4279. }
  4280. /* Style pour les boutons de mode de jeu qui ont une image de fond */
  4281. .btn-mode-cobalt,
  4282. [style*="background: url("] {
  4283. background-repeat: no-repeat !important;
  4284. background-position: right center !important;
  4285. background-size: auto 70% !important;
  4286. position: relative !important;
  4287. padding-right: 8px !important;
  4288. }
  4289. #btn-start-mode-0 {
  4290. background-repeat: initial !important;
  4291. background-position: initial !important;
  4292. background-size: initial !important;
  4293. padding-right: initial !important;
  4294. }
  4295. `;
  4296. const customStylesDesktop = `
  4297. .ui-game-menu-desktop {
  4298. background: linear-gradient(135deg, rgba(25, 25, 35, 0.95) 0%, rgba(15, 15, 25, 0.98) 100%) !important;
  4299. border: 1px solid rgba(255, 255, 255, 0.1) !important;
  4300. border-radius: 12px !important;
  4301. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3) !important;
  4302. padding: 20px !important;
  4303. backdrop-filter: blur(10px) !important;
  4304. max-width: 350px !important;
  4305. /* max-height: 80vh !important; */ /* Optional: Limit the maximum height */
  4306. margin: auto !important;
  4307. box-sizing: border-box !important;
  4308. overflow-y: auto !important; /* Allow vertical scrolling if necessary */
  4309. }
  4310.  
  4311. /* Style pour les boutons de mode de jeu qui ont une image de fond */
  4312. .btn-mode-cobalt,
  4313. [style*="background: url("] {
  4314. background-repeat: no-repeat !important;
  4315. background-position: right center !important;
  4316. background-size: auto 80% !important;
  4317. position: relative !important;
  4318. padding-right: 40px !important;
  4319. }
  4320.  
  4321. /* Ne pas appliquer ce style aux boutons standards comme Play Solo */
  4322. #btn-start-mode-0 {
  4323. background-repeat: initial !important;
  4324. background-position: initial !important;
  4325. background-size: initial !important;
  4326. padding-right: initial !important;
  4327. }
  4328.  
  4329. .ui-game-menu-desktop::-webkit-scrollbar {
  4330. width: 8px !important;
  4331. }
  4332. .ui-game-menu-desktop::-webkit-scrollbar-track {
  4333. background: rgba(25, 25, 35, 0.5) !important;
  4334. border-radius: 10px !important;
  4335. }
  4336. .ui-game-menu-desktop::-webkit-scrollbar-thumb {
  4337. background-color: #4287f5 !important;
  4338. border-radius: 10px !important;
  4339. border: 2px solid rgba(25, 25, 35, 0.5) !important;
  4340. }
  4341. .ui-game-menu-desktop::-webkit-scrollbar-thumb:hover {
  4342. background-color: #5a9eff !important;
  4343. }
  4344.  
  4345. .ui-game-menu-desktop {
  4346. scrollbar-width: thin !important;
  4347. scrollbar-color: #4287f5 rgba(25, 25, 35, 0.5) !important;
  4348. }
  4349.  
  4350. .kxs-header {
  4351. display: flex;
  4352. align-items: center;
  4353. justify-content: flex-start;
  4354. margin-bottom: 20px;
  4355. padding: 10px;
  4356. border-bottom: 2px solid rgba(255, 255, 255, 0.1);
  4357. }
  4358.  
  4359. .kxs-logo {
  4360. width: 30px;
  4361. height: 30px;
  4362. margin-right: 10px;
  4363. border-radius: 6px;
  4364. }
  4365.  
  4366. .kxs-title {
  4367. font-size: 20px;
  4368. font-weight: 700;
  4369. color: #ffffff;
  4370. text-transform: uppercase;
  4371. text-shadow: 0 0 10px rgba(66, 135, 245, 0.5);
  4372. font-family: 'Arial', sans-serif;
  4373. letter-spacing: 2px;
  4374. }
  4375.  
  4376. .kxs-title span {
  4377. color: #4287f5;
  4378. }
  4379.  
  4380. .btn-game-menu {
  4381. background: linear-gradient(135deg, rgba(66, 135, 245, 0.1) 0%, rgba(66, 135, 245, 0.2) 100%) !important;
  4382. border: 1px solid rgba(66, 135, 245, 0.3) !important;
  4383. border-radius: 8px !important;
  4384. color: #ffffff !important;
  4385. transition: all 0.3s ease !important;
  4386. margin: 5px 0 !important;
  4387. padding: 12px !important;
  4388. font-weight: 600 !important;
  4389. width: 100% !important;
  4390. text-align: center !important;
  4391. display: block !important;
  4392. box-sizing: border-box !important;
  4393. line-height: 15px !important;
  4394. }
  4395.  
  4396. .btn-game-menu:hover {
  4397. background: linear-gradient(135deg, rgba(66, 135, 245, 0.2) 0%, rgba(66, 135, 245, 0.3) 100%) !important;
  4398. transform: translateY(-2px) !important;
  4399. box-shadow: 0 4px 12px rgba(66, 135, 245, 0.2) !important;
  4400. }
  4401.  
  4402. .slider-container {
  4403. background: rgba(66, 135, 245, 0.1) !important;
  4404. border-radius: 8px !important;
  4405. padding: 10px 15px !important;
  4406. margin: 10px 0 !important;
  4407. width: 100% !important;
  4408. box-sizing: border-box !important;
  4409. }
  4410.  
  4411. .slider-text {
  4412. color: #ffffff !important;
  4413. font-size: 14px !important;
  4414. margin-bottom: 8px !important;
  4415. text-align: center !important;
  4416. }
  4417.  
  4418. .slider {
  4419. -webkit-appearance: none !important;
  4420. width: 100% !important;
  4421. height: 6px !important;
  4422. border-radius: 3px !important;
  4423. background: rgba(66, 135, 245, 0.3) !important;
  4424. outline: none !important;
  4425. margin: 10px 0 !important;
  4426. }
  4427.  
  4428. .slider::-webkit-slider-thumb {
  4429. -webkit-appearance: none !important;
  4430. width: 16px !important;
  4431. height: 16px !important;
  4432. border-radius: 50% !important;
  4433. background: #4287f5 !important;
  4434. cursor: pointer !important;
  4435. transition: all 0.3s ease !important;
  4436. }
  4437.  
  4438. .slider::-webkit-slider-thumb:hover {
  4439. transform: scale(1.2) !important;
  4440. box-shadow: 0 0 10px rgba(66, 135, 245, 0.5) !important;
  4441. }
  4442.  
  4443. .btns-game-double-row {
  4444. display: flex !important;
  4445. justify-content: center !important;
  4446. gap: 10px !important;
  4447. margin-bottom: 10px !important;
  4448. width: 100% !important;
  4449. }
  4450.  
  4451. .btn-game-container {
  4452. flex: 1 !important;
  4453. }
  4454.  
  4455. #btn-touch-styles,
  4456. #btn-game-aim-line {
  4457. display: none !important;
  4458. pointer-events: none !important;
  4459. visibility: hidden !important;
  4460. }
  4461. `;
  4462. const addCustomStyles = () => {
  4463. const styleElement = document.createElement('style');
  4464. styleElement.textContent = this.kxsClient.isMobile() ? customStylesMobile : customStylesDesktop;
  4465. document.head.appendChild(styleElement);
  4466. };
  4467. const addKxsHeader = () => {
  4468. const menuContainer = document.querySelector('#ui-game-menu');
  4469. if (!menuContainer)
  4470. return;
  4471. const header = document.createElement('div');
  4472. header.className = 'kxs-header';
  4473. const title = document.createElement('span');
  4474. title.className = 'kxs-title';
  4475. title.innerHTML = '<span>Kxs</span> CLIENT';
  4476. header.appendChild(title);
  4477. menuContainer.insertBefore(header, menuContainer.firstChild);
  4478. };
  4479. const disableUnwantedButtons = () => {
  4480. const touchStyles = document.getElementById('btn-touch-styles');
  4481. const aimLine = document.getElementById('btn-game-aim-line');
  4482. if (touchStyles) {
  4483. touchStyles.style.display = 'none';
  4484. touchStyles.style.pointerEvents = 'none';
  4485. touchStyles.style.visibility = 'hidden';
  4486. }
  4487. if (aimLine) {
  4488. aimLine.style.display = 'none';
  4489. aimLine.style.pointerEvents = 'none';
  4490. aimLine.style.visibility = 'hidden';
  4491. }
  4492. };
  4493. if (document.querySelector('#ui-game-menu')) {
  4494. addCustomStyles();
  4495. addKxsHeader();
  4496. if (!this.kxsClient.isMobile()) {
  4497. disableUnwantedButtons();
  4498. }
  4499. // Désactiver uniquement le slider Music Volume
  4500. const sliders = document.querySelectorAll('.slider-container.ui-slider-container');
  4501. sliders.forEach(slider => {
  4502. const label = slider.querySelector('p.slider-text[data-l10n="index-music-volume"]');
  4503. if (label) {
  4504. slider.style.display = 'none';
  4505. }
  4506. });
  4507. // Ajout du bouton Toggle Right Shift Menu
  4508. const menuContainer = document.querySelector('#ui-game-menu');
  4509. if (menuContainer) {
  4510. const toggleRightShiftBtn = document.createElement('button');
  4511. toggleRightShiftBtn.textContent = 'Toggle Right Shift Menu';
  4512. toggleRightShiftBtn.className = 'btn-game-menu';
  4513. toggleRightShiftBtn.style.marginTop = '10px';
  4514. toggleRightShiftBtn.onclick = () => {
  4515. if (this.kxsClient.secondaryMenu && typeof this.kxsClient.secondaryMenu.toggleMenuVisibility === 'function') {
  4516. this.kxsClient.secondaryMenu.toggleMenuVisibility();
  4517. }
  4518. };
  4519. menuContainer.appendChild(toggleRightShiftBtn);
  4520. }
  4521. }
  4522. }
  4523. handleMessage(element) {
  4524. if (element instanceof HTMLElement && element.classList.contains('killfeed-div')) {
  4525. const killfeedText = element.querySelector('.killfeed-text');
  4526. if (killfeedText instanceof HTMLElement) {
  4527. if (killfeedText.textContent && killfeedText.textContent.trim() !== '') {
  4528. if (!killfeedText.hasAttribute('data-glint')) {
  4529. killfeedText.setAttribute('data-glint', 'true');
  4530. element.style.opacity = '1';
  4531. setTimeout(() => {
  4532. element.style.opacity = '0';
  4533. }, 5000);
  4534. }
  4535. }
  4536. else {
  4537. element.style.opacity = '0';
  4538. }
  4539. }
  4540. }
  4541. }
  4542. setupObserver() {
  4543. const killfeedContents = document.getElementById('ui-killfeed-contents');
  4544. if (killfeedContents) {
  4545. // Détruit l'ancien observer s'il existe
  4546. if (this.killFeedObserver) {
  4547. this.killFeedObserver.disconnect();
  4548. }
  4549. this.killFeedObserver = new MutationObserver((mutations) => {
  4550. mutations.forEach((mutation) => {
  4551. if (mutation.target instanceof HTMLElement &&
  4552. mutation.target.classList.contains('killfeed-text')) {
  4553. const parentDiv = mutation.target.closest('.killfeed-div');
  4554. if (parentDiv) {
  4555. this.handleMessage(parentDiv);
  4556. }
  4557. }
  4558. mutation.addedNodes.forEach((node) => {
  4559. if (node instanceof HTMLElement) {
  4560. this.handleMessage(node);
  4561. }
  4562. });
  4563. });
  4564. });
  4565. this.killFeedObserver.observe(killfeedContents, {
  4566. childList: true,
  4567. subtree: true,
  4568. characterData: true,
  4569. attributes: true,
  4570. attributeFilter: ['style', 'class']
  4571. });
  4572. killfeedContents.querySelectorAll('.killfeed-div').forEach(this.handleMessage);
  4573. }
  4574. }
  4575. /**
  4576. * Détruit l'observer du killfeed s'il existe
  4577. */
  4578. disableKillFeedObserver() {
  4579. if (this.killFeedObserver) {
  4580. this.killFeedObserver.disconnect();
  4581. this.killFeedObserver = null;
  4582. }
  4583. }
  4584. applyCustomStyles() {
  4585. const customStyles = document.createElement('style');
  4586. if (this.kxsClient.isKillFeedBlint) {
  4587. customStyles.innerHTML = `
  4588. @import url('https://fonts.googleapis.com/css2?family=Oxanium:wght@600&display=swap');
  4589. .killfeed-div {
  4590. position: absolute !important;
  4591. padding: 5px 10px !important;
  4592. background: rgba(0, 0, 0, 0.7) !important;
  4593. border-radius: 5px !important;
  4594. transition: opacity 0.5s ease-out !important;
  4595. }
  4596. .killfeed-text {
  4597. font-family: 'Oxanium', sans-serif !important;
  4598. font-weight: bold !important;
  4599. font-size: 16px !important;
  4600. text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5) !important;
  4601. background: linear-gradient(90deg,
  4602. rgb(255, 0, 0),
  4603. rgb(255, 127, 0),
  4604. rgb(255, 255, 0),
  4605. rgb(0, 255, 0),
  4606. rgb(0, 0, 255),
  4607. rgb(75, 0, 130),
  4608. rgb(148, 0, 211),
  4609. rgb(255, 0, 0));
  4610. background-size: 200%;
  4611. -webkit-background-clip: text;
  4612. -webkit-text-fill-color: transparent;
  4613. animation: glint 3s linear infinite;
  4614. }
  4615. @keyframes glint {
  4616. 0% {
  4617. background-position: 200% 0;
  4618. }
  4619. 100% {
  4620. background-position: -200% 0;
  4621. }
  4622. }
  4623. .killfeed-div .killfeed-text:empty {
  4624. display: none !important;
  4625. }
  4626. `;
  4627. }
  4628. else {
  4629. customStyles.innerHTML = `
  4630. .killfeed-div {
  4631. position: absolute;
  4632. padding: 5px 10px;
  4633. background: rgba(0, 0, 0, 0.7);
  4634. border-radius: 5px;
  4635. transition: opacity 0.5s ease-out;
  4636. }
  4637. .killfeed-text {
  4638. font-family: inherit;
  4639. font-weight: normal;
  4640. font-size: inherit;
  4641. color: inherit;
  4642. text-shadow: none;
  4643. background: none;
  4644. }
  4645. .killfeed-div .killfeed-text:empty {
  4646. display: none;
  4647. }
  4648. `;
  4649. }
  4650. document.head.appendChild(customStyles);
  4651. }
  4652. handleResize() {
  4653. const viewportWidth = window.innerWidth;
  4654. const viewportHeight = window.innerHeight;
  4655. for (const name of ['fps', 'kills', 'ping']) {
  4656. const counterContainer = document.getElementById(`${name}CounterContainer`);
  4657. if (!counterContainer)
  4658. continue;
  4659. const counter = this.kxsClient.counters[name];
  4660. if (!counter)
  4661. continue;
  4662. const rect = counterContainer.getBoundingClientRect();
  4663. const savedPosition = this.getSavedPosition(name);
  4664. let newPosition = this.calculateSafePosition(savedPosition, rect.width, rect.height, viewportWidth, viewportHeight);
  4665. this.applyPosition(counterContainer, newPosition);
  4666. this.savePosition(name, newPosition);
  4667. }
  4668. }
  4669. calculateSafePosition(currentPosition, elementWidth, elementHeight, viewportWidth, viewportHeight) {
  4670. let { left, top } = currentPosition;
  4671. if (left + elementWidth > viewportWidth) {
  4672. left = viewportWidth - elementWidth;
  4673. }
  4674. if (left < 0) {
  4675. left = 0;
  4676. }
  4677. if (top + elementHeight > viewportHeight) {
  4678. top = viewportHeight - elementHeight;
  4679. }
  4680. if (top < 0) {
  4681. top = 0;
  4682. }
  4683. return { left, top };
  4684. }
  4685. getSavedPosition(name) {
  4686. const savedPosition = localStorage.getItem(`${name}CounterPosition`);
  4687. if (savedPosition) {
  4688. try {
  4689. return JSON.parse(savedPosition);
  4690. }
  4691. catch (_a) {
  4692. return this.kxsClient.defaultPositions[name];
  4693. }
  4694. }
  4695. return this.kxsClient.defaultPositions[name];
  4696. }
  4697. applyPosition(element, position) {
  4698. element.style.left = `${position.left}px`;
  4699. element.style.top = `${position.top}px`;
  4700. }
  4701. savePosition(name, position) {
  4702. localStorage.setItem(`${name}CounterPosition`, JSON.stringify(position));
  4703. }
  4704. startUpdateLoop() {
  4705. var _a;
  4706. const now = performance.now();
  4707. const delta = now - this.kxsClient.lastFrameTime;
  4708. this.frameCount++;
  4709. if (delta >= 1000) {
  4710. this.fps = Math.round((this.frameCount * 1000) / delta);
  4711. this.frameCount = 0;
  4712. this.kxsClient.lastFrameTime = now;
  4713. this.kills = this.kxsClient.getKills();
  4714. if (this.kxsClient.isFpsVisible && this.kxsClient.counters.fps) {
  4715. this.kxsClient.counters.fps.textContent = `FPS: ${this.fps}`;
  4716. }
  4717. if (this.kxsClient.isKillsVisible && this.kxsClient.counters.kills) {
  4718. this.kxsClient.counters.kills.textContent = `Kills: ${this.kills}`;
  4719. }
  4720. if (this.kxsClient.isPingVisible &&
  4721. this.kxsClient.counters.ping &&
  4722. this.pingManager) {
  4723. const result = this.pingManager.getPingResult();
  4724. this.kxsClient.counters.ping.textContent = `PING: ${result.ping} ms`;
  4725. }
  4726. }
  4727. if (this.kxsClient.animationFrameCallback) {
  4728. this.kxsClient.animationFrameCallback(() => this.startUpdateLoop());
  4729. }
  4730. this.updateUiElements();
  4731. this.updateBoostBars();
  4732. this.updateHealthBars();
  4733. (_a = this.kxsClient.kill_leader) === null || _a === void 0 ? void 0 : _a.update(this.kills);
  4734. }
  4735. initCounter(name, label, initialText) {
  4736. const counter = document.createElement("div");
  4737. counter.id = `${name}Counter`;
  4738. const counterContainer = document.createElement("div");
  4739. counterContainer.id = `${name}CounterContainer`;
  4740. Object.assign(counterContainer.style, {
  4741. position: "absolute",
  4742. left: `${this.kxsClient.defaultPositions[name].left}px`,
  4743. top: `${this.kxsClient.defaultPositions[name].top}px`,
  4744. zIndex: "10000",
  4745. });
  4746. Object.assign(counter.style, {
  4747. color: "white",
  4748. backgroundColor: "rgba(0, 0, 0, 0.2)",
  4749. borderRadius: "5px",
  4750. fontFamily: "Arial, sans-serif",
  4751. padding: "5px 10px",
  4752. pointerEvents: "none",
  4753. cursor: "default",
  4754. width: `${this.kxsClient.defaultSizes[name].width}px`,
  4755. height: `${this.kxsClient.defaultSizes[name].height}px`,
  4756. display: "flex",
  4757. alignItems: "center",
  4758. justifyContent: "center",
  4759. textAlign: "center",
  4760. resize: "both",
  4761. overflow: "hidden",
  4762. });
  4763. counter.textContent = `${label}: ${initialText}`;
  4764. counterContainer.appendChild(counter);
  4765. const uiTopLeft = document.getElementById("ui-top-left");
  4766. if (uiTopLeft) {
  4767. uiTopLeft.appendChild(counterContainer);
  4768. }
  4769. const adjustFontSize = () => {
  4770. const { width, height } = counter.getBoundingClientRect();
  4771. const size = Math.min(width, height) * 0.4;
  4772. counter.style.fontSize = `${size}px`;
  4773. };
  4774. new ResizeObserver(adjustFontSize).observe(counter);
  4775. counter.addEventListener("mousedown", (event) => {
  4776. if (event.button === 1) {
  4777. this.resetCounter(name, label, initialText);
  4778. event.preventDefault();
  4779. }
  4780. });
  4781. this.kxsClient.makeDraggable(counterContainer, `${name}CounterPosition`);
  4782. this.kxsClient.counters[name] = counter;
  4783. }
  4784. resetCounter(name, label, initialText) {
  4785. const counter = this.kxsClient.counters[name];
  4786. const container = document.getElementById(`${name}CounterContainer`);
  4787. if (!counter || !container)
  4788. return;
  4789. // Reset only this counter's position and size
  4790. Object.assign(container.style, {
  4791. left: `${this.kxsClient.defaultPositions[name].left}px`,
  4792. top: `${this.kxsClient.defaultPositions[name].top}px`,
  4793. });
  4794. Object.assign(counter.style, {
  4795. width: `${this.kxsClient.defaultSizes[name].width}px`,
  4796. height: `${this.kxsClient.defaultSizes[name].height}px`,
  4797. fontSize: "18px",
  4798. });
  4799. counter.textContent = `${label}: ${initialText}`;
  4800. // Clear the saved position for this counter only
  4801. localStorage.removeItem(`${name}CounterPosition`);
  4802. }
  4803. updateBoostBars() {
  4804. const boostCounter = document.querySelector("#ui-boost-counter");
  4805. if (boostCounter) {
  4806. // Si les indicateurs sont désactivés, on supprime les éléments personnalisés
  4807. if (!this.kxsClient.isHealBarIndicatorEnabled) {
  4808. this.cleanBoostDisplay(boostCounter);
  4809. return;
  4810. }
  4811. const boostBars = boostCounter.querySelectorAll(".ui-boost-base .ui-bar-inner");
  4812. let totalBoost = 0;
  4813. const weights = [25, 25, 40, 10];
  4814. boostBars.forEach((bar, index) => {
  4815. const width = parseFloat(bar.style.width);
  4816. if (!isNaN(width)) {
  4817. totalBoost += width * (weights[index] / 100);
  4818. }
  4819. });
  4820. const averageBoost = Math.round(totalBoost);
  4821. let boostDisplay = boostCounter.querySelector(".boost-display");
  4822. if (!boostDisplay) {
  4823. boostDisplay = document.createElement("div");
  4824. boostDisplay.classList.add("boost-display");
  4825. Object.assign(boostDisplay.style, {
  4826. position: "absolute",
  4827. bottom: "75px",
  4828. right: "335px",
  4829. color: "#FF901A",
  4830. backgroundColor: "rgba(0, 0, 0, 0.4)",
  4831. padding: "5px 10px",
  4832. borderRadius: "5px",
  4833. fontFamily: "Arial, sans-serif",
  4834. fontSize: "14px",
  4835. zIndex: "10",
  4836. textAlign: "center",
  4837. });
  4838. boostCounter.appendChild(boostDisplay);
  4839. }
  4840. boostDisplay.textContent = `AD: ${averageBoost}%`;
  4841. }
  4842. }
  4843. toggleWeaponBorderHandler() {
  4844. // Get all weapon containers
  4845. const weaponContainers = Array.from(document.getElementsByClassName("ui-weapon-switch"));
  4846. // Get all weapon names
  4847. const weaponNames = Array.from(document.getElementsByClassName("ui-weapon-name"));
  4848. // Clear any existing observers
  4849. this.clearWeaponBorderObservers();
  4850. if (this.kxsClient.isGunOverlayColored) {
  4851. // Apply initial border colors
  4852. weaponContainers.forEach((container) => {
  4853. if (container.id === "ui-weapon-id-4") {
  4854. container.style.border = "3px solid #2f4032";
  4855. }
  4856. else {
  4857. container.style.border = "3px solid #FFFFFF";
  4858. }
  4859. });
  4860. const WEAPON_COLORS = {
  4861. ORANGE: '#FFAE00',
  4862. BLUE: '#007FFF',
  4863. GREEN: '#0f690d',
  4864. RED: '#FF0000',
  4865. BLACK: '#000000',
  4866. OLIVE: '#808000',
  4867. ORANGE_RED: '#FF4500',
  4868. PURPLE: '#800080',
  4869. TEAL: '#008080',
  4870. BROWN: '#A52A2A',
  4871. PINK: '#FFC0CB',
  4872. DEFAULT: '#FFFFFF'
  4873. };
  4874. const WEAPON_COLOR_MAPPING = {
  4875. ORANGE: ['CZ-3A1', 'G18C', 'M9', 'M93R', 'MAC-10', 'MP5', 'P30L', 'DUAL P30L', 'UMP9', 'VECTOR', 'VSS', 'FLAMETHROWER'],
  4876. BLUE: ['AK-47', 'OT-38', 'OTS-38', 'M39 EMR', 'DP-28', 'MOSIN-NAGANT', 'SCAR-H', 'SV-98', 'M1 GARAND', 'PKP PECHENEG', 'AN-94', 'BAR M1918', 'BLR 81', 'SVD-63', 'M134', 'WATER GUN', 'GROZA', 'GROZA-S'],
  4877. GREEN: ['FAMAS', 'M416', 'M249', 'QBB-97', 'MK 12 SPR', 'M4A1-S', 'SCOUT ELITE', 'L86A2'],
  4878. RED: ['M870', 'MP220', 'SAIGA-12', 'SPAS-12', 'USAS-12', 'SUPER 90', 'LASR GUN', 'M1100'],
  4879. BLACK: ['DEAGLE 50', 'RAINBOW BLASTER'],
  4880. OLIVE: ['AWM-S', 'MK 20 SSR'],
  4881. ORANGE_RED: ['FLARE GUN'],
  4882. PURPLE: ['MODEL 94', 'PEACEMAKER', 'VECTOR (.45 ACP)', 'M1911', 'M1A1', 'MK45G'],
  4883. TEAL: ['M79'],
  4884. BROWN: ['POTATO CANNON', 'SPUD GUN'],
  4885. PINK: ['HEART CANNON'],
  4886. DEFAULT: []
  4887. };
  4888. // Set up observers for dynamic color changes
  4889. weaponNames.forEach((weaponNameElement) => {
  4890. const weaponContainer = weaponNameElement.closest(".ui-weapon-switch");
  4891. const observer = new MutationObserver(() => {
  4892. var _a, _b, _c;
  4893. const weaponName = ((_b = (_a = weaponNameElement.textContent) === null || _a === void 0 ? void 0 : _a.trim()) === null || _b === void 0 ? void 0 : _b.toUpperCase()) || '';
  4894. let colorKey = 'DEFAULT';
  4895. // Do a hack for "VECTOR" gun (because can be 2 weapons: yellow or purple)
  4896. if (weaponName === "VECTOR") {
  4897. // Get the weapon container and image element
  4898. const weaponContainer = weaponNameElement.closest(".ui-weapon-switch");
  4899. const weaponImage = weaponContainer === null || weaponContainer === void 0 ? void 0 : weaponContainer.querySelector(".ui-weapon-image");
  4900. if (weaponImage && weaponImage.src) {
  4901. // Check the image source to determine which Vector it is
  4902. if (weaponImage.src.includes("-acp") || weaponImage.src.includes("45")) {
  4903. colorKey = 'PURPLE';
  4904. }
  4905. else {
  4906. colorKey = 'ORANGE';
  4907. }
  4908. }
  4909. else {
  4910. // Default to orange if we can't determine the type
  4911. colorKey = 'ORANGE';
  4912. }
  4913. }
  4914. else {
  4915. colorKey = (((_c = Object.entries(WEAPON_COLOR_MAPPING)
  4916. .find(([_, weapons]) => weapons.includes(weaponName))) === null || _c === void 0 ? void 0 : _c[0]) || 'DEFAULT');
  4917. }
  4918. if (weaponContainer && weaponContainer.id !== "ui-weapon-id-4") {
  4919. weaponContainer.style.border = `3px solid ${WEAPON_COLORS[colorKey]}`;
  4920. }
  4921. });
  4922. observer.observe(weaponNameElement, { childList: true, characterData: true, subtree: true });
  4923. // Store the observer for later cleanup
  4924. this.weaponBorderObservers = this.weaponBorderObservers || [];
  4925. this.weaponBorderObservers.push(observer);
  4926. });
  4927. }
  4928. else {
  4929. // If the feature is disabled, reset all weapon borders to default
  4930. weaponContainers.forEach((container) => {
  4931. // Reset to game's default border style
  4932. container.style.border = "";
  4933. });
  4934. }
  4935. }
  4936. // Helper method to clear weapon border observers
  4937. clearWeaponBorderObservers() {
  4938. if (this.weaponBorderObservers && this.weaponBorderObservers.length > 0) {
  4939. this.weaponBorderObservers.forEach(observer => {
  4940. observer.disconnect();
  4941. });
  4942. this.weaponBorderObservers = [];
  4943. }
  4944. }
  4945. toggleChromaticWeaponBorder() {
  4946. const borderClass = 'kxs-chromatic-border';
  4947. const styleId = 'kxs-chromatic-border-style';
  4948. const weaponIds = [1, 2, 3, 4];
  4949. if (this.kxsClient.isGunBorderChromatic) {
  4950. // Inject CSS if not already present
  4951. if (!document.getElementById(styleId)) {
  4952. const style = document.createElement('style');
  4953. style.id = styleId;
  4954. style.innerHTML = `
  4955. @keyframes kxs-rainbow {
  4956. 0% { border-image: linear-gradient(120deg, #ff004c, #fffa00, #00ff90, #004cff, #ff004c) 1; }
  4957. 100% { border-image: linear-gradient(480deg, #ff004c, #fffa00, #00ff90, #004cff, #ff004c) 1; }
  4958. }
  4959. @keyframes kxs-glint {
  4960. 0% { box-shadow: 0 0 8px 2px #fff2; }
  4961. 50% { box-shadow: 0 0 24px 6px #fff8; }
  4962. 100% { box-shadow: 0 0 8px 2px #fff2; }
  4963. }
  4964. @keyframes kxs-bg-rainbow {
  4965. 0% { background-position: 0% 50%; }
  4966. 50% { background-position: 100% 50%; }
  4967. 100% { background-position: 0% 50%; }
  4968. }
  4969. .kxs-chromatic-border {
  4970. border: 3px solid transparent !important;
  4971. border-image: linear-gradient(120deg, #ff004c, #fffa00, #00ff90, #004cff, #ff004c) 1;
  4972. animation: kxs-rainbow 3s linear infinite, kxs-glint 2s ease-in-out infinite, kxs-bg-rainbow 8s linear infinite;
  4973. border-radius: 8px !important;
  4974. background: linear-gradient(270deg, #ff004c, #fffa00, #00ff90, #004cff, #ff004c);
  4975. background-size: 1200% 1200%;
  4976. background-position: 0% 50%;
  4977. background-clip: padding-box;
  4978. -webkit-background-clip: padding-box;
  4979. filter: brightness(1.15) saturate(1.4);
  4980. transition: background 0.5s;
  4981. }
  4982. `;
  4983. document.head.appendChild(style);
  4984. }
  4985. weaponIds.forEach(id => {
  4986. const el = document.getElementById(`ui-weapon-id-${id}`);
  4987. if (el) {
  4988. el.classList.add(borderClass);
  4989. }
  4990. });
  4991. }
  4992. else {
  4993. // Remove chromatic border and style
  4994. weaponIds.forEach(id => {
  4995. const el = document.getElementById(`ui-weapon-id-${id}`);
  4996. if (el) {
  4997. el.classList.remove(borderClass);
  4998. el.style.border = '';
  4999. }
  5000. });
  5001. const style = document.getElementById(styleId);
  5002. if (style)
  5003. style.remove();
  5004. // Reapply regular colored borders if that feature is enabled
  5005. if (this.kxsClient.isGunOverlayColored) {
  5006. this.toggleWeaponBorderHandler();
  5007. }
  5008. }
  5009. }
  5010. updateUiElements() {
  5011. // Réapplique l'effet chromatique si activé (corrige le bug d'affichage après un changement de page ou entrée en game)
  5012. if (this.kxsClient.isGunBorderChromatic) {
  5013. this.toggleChromaticWeaponBorder();
  5014. }
  5015. const currentUrl = window.location.href;
  5016. const isSpecialUrl = /\/#\w+/.test(currentUrl);
  5017. const playerOptions = document.getElementById("player-options");
  5018. const teamMenuContents = document.getElementById("team-menu-contents");
  5019. const startMenuContainer = document.querySelector("#start-menu .play-button-container");
  5020. // Update counters draggable state based on LSHIFT menu visibility
  5021. this.updateCountersDraggableState();
  5022. if (!playerOptions)
  5023. return;
  5024. if (isSpecialUrl &&
  5025. teamMenuContents &&
  5026. playerOptions.parentNode !== teamMenuContents) {
  5027. teamMenuContents.appendChild(playerOptions);
  5028. }
  5029. else if (!isSpecialUrl &&
  5030. startMenuContainer &&
  5031. playerOptions.parentNode !== startMenuContainer) {
  5032. const firstChild = startMenuContainer.firstChild;
  5033. startMenuContainer.insertBefore(playerOptions, firstChild);
  5034. }
  5035. const teamMenu = document.getElementById("team-menu");
  5036. if (teamMenu) {
  5037. teamMenu.style.height = "355px";
  5038. }
  5039. const menuBlocks = document.querySelectorAll(".menu-block");
  5040. menuBlocks.forEach((block) => {
  5041. block.style.maxHeight = "355px";
  5042. });
  5043. //scalable?
  5044. }
  5045. // Nettoie l'affichage boost personnalisé
  5046. cleanBoostDisplay(boostCounter) {
  5047. const boostDisplay = boostCounter.querySelector(".boost-display");
  5048. if (boostDisplay) {
  5049. boostDisplay.remove();
  5050. }
  5051. }
  5052. // Nettoie l'affichage santé personnalisé
  5053. cleanHealthDisplay(container) {
  5054. const percentageText = container.querySelector(".health-text");
  5055. if (percentageText) {
  5056. percentageText.remove();
  5057. }
  5058. const healthChangeElements = container.querySelectorAll(".health-change");
  5059. healthChangeElements.forEach(el => el.remove());
  5060. }
  5061. updateHealthBars() {
  5062. const healthBars = document.querySelectorAll("#ui-health-container");
  5063. healthBars.forEach((container) => {
  5064. var _a, _b;
  5065. // Si les indicateurs sont désactivés, on supprime les éléments personnalisés
  5066. if (!this.kxsClient.isHealBarIndicatorEnabled) {
  5067. this.cleanHealthDisplay(container);
  5068. return;
  5069. }
  5070. const bar = container.querySelector("#ui-health-actual");
  5071. if (bar) {
  5072. const currentHealth = Math.round(parseFloat(bar.style.width));
  5073. let percentageText = container.querySelector(".health-text");
  5074. // Create or update percentage text
  5075. if (!percentageText) {
  5076. percentageText = document.createElement("span");
  5077. percentageText.classList.add("health-text");
  5078. Object.assign(percentageText.style, {
  5079. width: "100%",
  5080. textAlign: "center",
  5081. marginTop: "5px",
  5082. color: "#333",
  5083. fontSize: "20px",
  5084. fontWeight: "bold",
  5085. position: "absolute",
  5086. zIndex: "10",
  5087. });
  5088. container.appendChild(percentageText);
  5089. }
  5090. // Check for health change
  5091. if (currentHealth !== this.lastHealthValue) {
  5092. const healthChange = currentHealth - this.lastHealthValue;
  5093. if (healthChange !== 0) {
  5094. this.showHealthChangeAnimation(container, healthChange);
  5095. }
  5096. this.lastHealthValue = currentHealth;
  5097. }
  5098. if (this.kxsClient.isHealthWarningEnabled) {
  5099. (_a = this.kxsClient.healWarning) === null || _a === void 0 ? void 0 : _a.update(currentHealth);
  5100. }
  5101. else {
  5102. (_b = this.kxsClient.healWarning) === null || _b === void 0 ? void 0 : _b.hide();
  5103. }
  5104. percentageText.textContent = `${currentHealth}%`;
  5105. // Update animations
  5106. this.updateHealthAnimations();
  5107. }
  5108. });
  5109. }
  5110. showHealthChangeAnimation(container, change) {
  5111. const healthContainer = container;
  5112. if (!healthContainer || !this.kxsClient.isHealBarIndicatorEnabled)
  5113. return;
  5114. // Create animation element
  5115. const animationElement = document.createElement("div");
  5116. animationElement.classList.add("health-change");
  5117. const isPositive = change > 0;
  5118. Object.assign(animationElement.style, {
  5119. position: "absolute",
  5120. color: isPositive ? "#2ecc71" : "#e74c3c",
  5121. fontSize: "24px",
  5122. fontWeight: "bold",
  5123. fontFamily: "Arial, sans-serif",
  5124. textShadow: "2px 2px 4px rgba(0,0,0,0.3)",
  5125. pointerEvents: "none",
  5126. zIndex: "100",
  5127. opacity: "1",
  5128. top: "50%",
  5129. right: "-80px", // Position à droite de la barre de vie
  5130. transform: "translateY(-50%)", // Centre verticalement
  5131. whiteSpace: "nowrap", // Empêche le retour à la ligne
  5132. });
  5133. // Check if change is a valid number before displaying it
  5134. if (!isNaN(change)) {
  5135. animationElement.textContent = `${isPositive ? "+" : ""}${change} HP`;
  5136. }
  5137. else {
  5138. // Skip showing animation if change is NaN
  5139. return;
  5140. }
  5141. container.appendChild(animationElement);
  5142. this.healthAnimations.push({
  5143. element: animationElement,
  5144. startTime: performance.now(),
  5145. duration: 1500, // Animation duration in milliseconds
  5146. value: change,
  5147. });
  5148. }
  5149. updateCountersDraggableState() {
  5150. var _a;
  5151. const isMenuOpen = ((_a = this.kxsClient.secondaryMenu) === null || _a === void 0 ? void 0 : _a.getMenuVisibility()) || false;
  5152. const counters = ['fps', 'kills', 'ping'];
  5153. counters.forEach(name => {
  5154. const counter = document.getElementById(`${name}Counter`);
  5155. if (counter) {
  5156. // Mise à jour des propriétés de draggabilité
  5157. counter.style.pointerEvents = isMenuOpen ? 'auto' : 'none';
  5158. counter.style.cursor = isMenuOpen ? 'move' : 'default';
  5159. // Mise à jour de la possibilité de redimensionnement
  5160. counter.style.resize = isMenuOpen ? 'both' : 'none';
  5161. }
  5162. });
  5163. }
  5164. updateHealthAnimations() {
  5165. const currentTime = performance.now();
  5166. this.healthAnimations = this.healthAnimations.filter(animation => {
  5167. const elapsed = currentTime - animation.startTime;
  5168. const progress = Math.min(elapsed / animation.duration, 1);
  5169. if (progress < 1) {
  5170. // Update animation position and opacity
  5171. // Maintenant l'animation se déplace horizontalement vers la droite
  5172. const translateX = progress * 20; // Déplacement horizontal
  5173. Object.assign(animation.element.style, {
  5174. transform: `translateY(-50%) translateX(${translateX}px)`,
  5175. opacity: String(1 - progress),
  5176. });
  5177. return true;
  5178. }
  5179. else {
  5180. // Remove completed animation
  5181. animation.element.remove();
  5182. return false;
  5183. }
  5184. });
  5185. }
  5186. }
  5187.  
  5188.  
  5189. ;// ./src/FUNC/Logger.ts
  5190. class Logger {
  5191. getHeader(method) {
  5192. return "[" + "KxsClient" + " - " + method + "]";
  5193. }
  5194. 展示(...args) {
  5195. console.log(...args);
  5196. }
  5197. ;
  5198. log(...args) {
  5199. // Convert args to string and join them
  5200. const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ');
  5201. this.展示(this.getHeader("LOG"), message);
  5202. }
  5203. warn(...args) {
  5204. const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ');
  5205. this.展示(this.getHeader("WARN"), message);
  5206. }
  5207. error(...args) {
  5208. const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ');
  5209. this.展示(this.getHeader("ERROR"), message);
  5210. }
  5211. }
  5212.  
  5213.  
  5214. // EXTERNAL MODULE: ./node_modules/stegano.db/lib/browser.js
  5215. var browser = __webpack_require__(814);
  5216. ;// ./src/HUD/HistoryManager.ts
  5217. var HistoryManager_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
  5218. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  5219. return new (P || (P = Promise))(function (resolve, reject) {
  5220. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  5221. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  5222. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  5223. step((generator = generator.apply(thisArg, _arguments || [])).next());
  5224. });
  5225. };
  5226. class GameHistoryMenu {
  5227. constructor(kxsClient) {
  5228. this.kxsClient = kxsClient;
  5229. this.container = document.createElement('div');
  5230. this.closeBtn = document.createElement('button');
  5231. }
  5232. initContainer() {
  5233. const isMobile = this.kxsClient.isMobile && this.kxsClient.isMobile();
  5234. // Position the menu in the center of the screen
  5235. Object.assign(this.container.style, {
  5236. position: 'fixed',
  5237. top: '50%',
  5238. left: '50%',
  5239. transform: 'translate(-50%, -50%)',
  5240. width: isMobile ? '78vw' : '700px',
  5241. maxWidth: isMobile ? '84vw' : '90vw',
  5242. maxHeight: isMobile ? '60vh' : '80vh',
  5243. overflowY: 'auto',
  5244. overflowX: 'hidden',
  5245. backgroundColor: 'rgba(17, 24, 39, 0.95)',
  5246. color: '#fff',
  5247. borderRadius: isMobile ? '7px' : '12px',
  5248. border: '1px solid rgba(60, 80, 120, 0.3)',
  5249. boxShadow: '0 4px 20px rgba(0,0,0,0.8)',
  5250. zIndex: '10001',
  5251. padding: isMobile ? '6px' : '20px',
  5252. boxSizing: 'border-box',
  5253. fontFamily: "'Segoe UI', Arial, sans-serif",
  5254. });
  5255. }
  5256. addCloseButton() {
  5257. this.closeBtn.textContent = '✖';
  5258. Object.assign(this.closeBtn.style, {
  5259. position: 'absolute',
  5260. top: '10px',
  5261. right: '15px',
  5262. background: 'transparent',
  5263. color: '#fff',
  5264. border: 'none',
  5265. fontSize: '22px',
  5266. cursor: 'pointer',
  5267. transition: 'transform 0.2s ease, color 0.2s ease',
  5268. zIndex: '10',
  5269. });
  5270. this.closeBtn.onclick = (e) => {
  5271. e.stopPropagation();
  5272. this.hide();
  5273. };
  5274. this.closeBtn.onmouseenter = () => {
  5275. this.closeBtn.style.color = '#3B82F6';
  5276. this.closeBtn.style.transform = 'scale(1.1)';
  5277. };
  5278. this.closeBtn.onmouseleave = () => {
  5279. this.closeBtn.style.color = '#fff';
  5280. this.closeBtn.style.transform = 'scale(1)';
  5281. };
  5282. this.container.appendChild(this.closeBtn);
  5283. }
  5284. addHeader() {
  5285. // Create a title area at the top of the menu (header)
  5286. const header = document.createElement('div');
  5287. header.style.position = 'absolute';
  5288. header.style.top = '0';
  5289. header.style.left = '0';
  5290. header.style.right = '0';
  5291. header.style.height = '40px';
  5292. // No border at the bottom
  5293. // Add a centered title
  5294. const title = document.createElement('div');
  5295. title.textContent = 'Game History';
  5296. title.style.position = 'absolute';
  5297. title.style.left = '50%';
  5298. title.style.top = '50%';
  5299. title.style.transform = 'translate(-50%, -50%)';
  5300. title.style.fontWeight = 'bold';
  5301. title.style.fontSize = '14px';
  5302. title.style.color = '#fff';
  5303. header.appendChild(title);
  5304. this.container.insertBefore(header, this.container.firstChild);
  5305. }
  5306. renderContent() {
  5307. return HistoryManager_awaiter(this, void 0, void 0, function* () {
  5308. // Header
  5309. const header = document.createElement('div');
  5310. header.textContent = 'Game History';
  5311. const isMobile = this.kxsClient.isMobile && this.kxsClient.isMobile();
  5312. Object.assign(header.style, {
  5313. fontWeight: 'bold',
  5314. fontSize: isMobile ? '1.1em' : '1.3em',
  5315. letterSpacing: '1px',
  5316. margin: isMobile ? '10px 0 12px 0' : '10px 0 20px 0',
  5317. textAlign: 'center',
  5318. color: '#3B82F6',
  5319. });
  5320. this.container.appendChild(header);
  5321. // Liste de l'historique
  5322. let historyList = document.createElement('div');
  5323. Object.assign(historyList.style, {
  5324. display: 'flex',
  5325. flexDirection: 'column',
  5326. gap: isMobile ? '6px' : '8px',
  5327. padding: isMobile ? '0 8px' : '0 15px',
  5328. width: '100%',
  5329. });
  5330. // Récupération de l'historique via SteganoDB
  5331. let result = this.kxsClient.db.all();
  5332. let entries = [];
  5333. // Traitement de la structure JSON gameplay_history
  5334. if (result && typeof result === 'object') {
  5335. // Vérifier si c'est la structure gameplay_history
  5336. if ('gameplay_history' in result && Array.isArray(result.gameplay_history)) {
  5337. entries = result.gameplay_history;
  5338. // Tri par date décroissante
  5339. entries.sort((a, b) => {
  5340. if (a.id && b.id)
  5341. return b.id.localeCompare(a.id);
  5342. return 0;
  5343. });
  5344. }
  5345. else {
  5346. // Fallback pour l'ancienne structure
  5347. entries = Array.isArray(result) ? result : [];
  5348. }
  5349. }
  5350. // Affichage ligne par ligne
  5351. if (!entries || entries.length === 0) {
  5352. const empty = document.createElement('div');
  5353. empty.textContent = 'No games recorded.';
  5354. empty.style.textAlign = 'center';
  5355. empty.style.color = '#aaa';
  5356. historyList.appendChild(empty);
  5357. }
  5358. else {
  5359. let i = 1;
  5360. for (const entry of entries) {
  5361. let key, value;
  5362. // Structure gameplay_history
  5363. if (typeof entry === 'object' && entry.id && entry.value) {
  5364. key = entry.id;
  5365. value = entry.value;
  5366. // Traitement des games dans value
  5367. if (typeof value === 'object') {
  5368. for (const gameId in value) {
  5369. const gameStats = value[gameId];
  5370. this.createGameHistoryLine(historyList, gameStats, key, i, isMobile);
  5371. i++;
  5372. }
  5373. continue; // Passer à l'entrée suivante après avoir traité tous les jeux
  5374. }
  5375. }
  5376. // Ancienne structure
  5377. else if (Array.isArray(entry) && entry.length === 2) {
  5378. key = entry[0];
  5379. value = entry[1];
  5380. }
  5381. else if (typeof entry === 'object' && entry.key && entry.value) {
  5382. key = entry.key;
  5383. value = entry.value;
  5384. }
  5385. else {
  5386. continue;
  5387. }
  5388. // Pour l'ancienne structure, créer une ligne
  5389. this.createGameHistoryLine(historyList, value, key, i, isMobile);
  5390. i++;
  5391. }
  5392. }
  5393. this.container.appendChild(historyList);
  5394. });
  5395. }
  5396. createGameHistoryLine(historyList, stats, dateKey, index, isMobile) {
  5397. const line = document.createElement('div');
  5398. Object.assign(line.style, {
  5399. background: index % 2 ? 'rgba(31, 41, 55, 0.7)' : 'rgba(17, 24, 39, 0.8)',
  5400. borderRadius: isMobile ? '5px' : '8px',
  5401. padding: isMobile ? '6px 8px' : '10px 15px',
  5402. fontFamily: "'Segoe UI', Arial, sans-serif",
  5403. fontSize: isMobile ? '0.8em' : '0.95em',
  5404. display: 'flex',
  5405. flexDirection: isMobile ? 'column' : 'row',
  5406. alignItems: isMobile ? 'flex-start' : 'center',
  5407. gap: isMobile ? '4px' : '12px',
  5408. transition: 'background 0.2s, transform 0.1s',
  5409. cursor: 'pointer',
  5410. border: '1px solid transparent',
  5411. });
  5412. // Effet hover
  5413. line.onmouseenter = () => {
  5414. line.style.background = 'rgba(59, 130, 246, 0.3)';
  5415. line.style.borderColor = 'rgba(59, 130, 246, 0.5)';
  5416. line.style.transform = 'translateY(-1px)';
  5417. };
  5418. line.onmouseleave = () => {
  5419. line.style.background = index % 2 ? 'rgba(31, 41, 55, 0.7)' : 'rgba(17, 24, 39, 0.8)';
  5420. line.style.borderColor = 'transparent';
  5421. line.style.transform = 'translateY(0)';
  5422. };
  5423. // Date formatée
  5424. const dateStr = dateKey ? new Date(dateKey).toLocaleString() : '';
  5425. const dateEl = document.createElement('div');
  5426. dateEl.textContent = dateStr;
  5427. Object.assign(dateEl.style, {
  5428. color: '#93c5fd',
  5429. fontWeight: 'bold',
  5430. whiteSpace: 'nowrap',
  5431. marginRight: isMobile ? '0' : '10px',
  5432. });
  5433. // Stats du jeu
  5434. const statsContainer = document.createElement('div');
  5435. Object.assign(statsContainer.style, {
  5436. display: 'flex',
  5437. flexDirection: isMobile ? 'column' : 'row',
  5438. flexWrap: 'wrap',
  5439. gap: isMobile ? '3px 8px' : '0 15px',
  5440. flex: '1',
  5441. });
  5442. // Création des éléments de stats
  5443. const createStatElement = (label, value, color = '#fff') => {
  5444. const statEl = document.createElement('div');
  5445. statEl.style.display = 'inline-flex';
  5446. statEl.style.alignItems = 'center';
  5447. statEl.style.marginRight = isMobile ? '5px' : '12px';
  5448. const labelEl = document.createElement('span');
  5449. labelEl.textContent = `${label}: `;
  5450. labelEl.style.color = '#9ca3af';
  5451. const valueEl = document.createElement('span');
  5452. valueEl.textContent = value !== undefined && value !== null ? String(value) : '-';
  5453. valueEl.style.color = color;
  5454. valueEl.style.fontWeight = 'bold';
  5455. statEl.appendChild(labelEl);
  5456. statEl.appendChild(valueEl);
  5457. return statEl;
  5458. };
  5459. // Ajout des stats avec couleurs
  5460. if (typeof stats === 'object') {
  5461. const isWin = stats.isWin === true;
  5462. statsContainer.appendChild(createStatElement('Player', stats.username, '#fff'));
  5463. statsContainer.appendChild(createStatElement('Kills', stats.kills, '#ef4444'));
  5464. statsContainer.appendChild(createStatElement('DMG', stats.damageDealt, '#f59e0b'));
  5465. statsContainer.appendChild(createStatElement('Taken', stats.damageTaken, '#a855f7'));
  5466. statsContainer.appendChild(createStatElement('Duration', stats.duration, '#fff'));
  5467. // Position avec couleur selon le rang
  5468. let posColor = '#fff';
  5469. if (stats.position) {
  5470. const pos = parseInt(stats.position.replace('#', ''));
  5471. if (pos <= 10)
  5472. posColor = '#fbbf24'; // Or
  5473. else if (pos <= 25)
  5474. posColor = '#94a3b8'; // Argent
  5475. else if (pos <= 50)
  5476. posColor = '#b45309'; // Bronze
  5477. }
  5478. statsContainer.appendChild(createStatElement('Pos', stats.position, posColor));
  5479. // Indicateur de victoire
  5480. if (isWin) {
  5481. const winEl = document.createElement('div');
  5482. winEl.textContent = '🏆 WIN';
  5483. winEl.style.color = '#fbbf24';
  5484. winEl.style.fontWeight = 'bold';
  5485. winEl.style.marginLeft = 'auto';
  5486. statsContainer.appendChild(winEl);
  5487. }
  5488. }
  5489. else {
  5490. // Fallback si stats n'est pas un objet
  5491. const fallbackEl = document.createElement('div');
  5492. fallbackEl.textContent = typeof stats === 'string' ? stats : JSON.stringify(stats);
  5493. statsContainer.appendChild(fallbackEl);
  5494. }
  5495. line.appendChild(dateEl);
  5496. line.appendChild(statsContainer);
  5497. historyList.appendChild(line);
  5498. }
  5499. show() {
  5500. // Recréer le conteneur pour un contenu frais
  5501. this.container = document.createElement('div');
  5502. this.closeBtn = document.createElement('button');
  5503. // Réinitialiser le conteneur
  5504. this.initContainer();
  5505. // Ajouter le bouton de fermeture
  5506. this.addCloseButton();
  5507. // Ajouter l'en-tête avec le titre
  5508. this.addHeader();
  5509. // Charger et afficher l'historique des jeux actualisé
  5510. this.renderContent();
  5511. // Close RSHIFT menu if it's open
  5512. if (this.kxsClient.secondaryMenu && typeof this.kxsClient.secondaryMenu.getMenuVisibility === 'function') {
  5513. if (this.kxsClient.secondaryMenu.getMenuVisibility()) {
  5514. this.kxsClient.secondaryMenu.toggleMenuVisibility();
  5515. }
  5516. }
  5517. // Prevent mouse event propagation
  5518. this.container.addEventListener('click', (e) => e.stopPropagation());
  5519. this.container.addEventListener('wheel', (e) => e.stopPropagation());
  5520. this.container.addEventListener('mousedown', (e) => e.stopPropagation());
  5521. this.container.addEventListener('mouseup', (e) => e.stopPropagation());
  5522. this.container.addEventListener('contextmenu', (e) => e.stopPropagation());
  5523. this.container.addEventListener('dblclick', (e) => e.stopPropagation());
  5524. // Center the menu on screen
  5525. this.container.style.top = '50%';
  5526. this.container.style.left = '50%';
  5527. this.container.style.transform = 'translate(-50%, -50%)';
  5528. // Add fade-in animation
  5529. this.container.style.opacity = '0';
  5530. this.container.style.transition = 'opacity 0.2s ease-in-out';
  5531. setTimeout(() => {
  5532. this.container.style.opacity = '1';
  5533. }, 10);
  5534. document.body.appendChild(this.container);
  5535. }
  5536. hide() {
  5537. // Clean up listeners before removing the menu
  5538. if (this.container) {
  5539. // Supprimer tous les gestionnaires d'événements
  5540. const allElements = this.container.querySelectorAll('*');
  5541. allElements.forEach(element => {
  5542. const el = element;
  5543. el.replaceWith(el.cloneNode(true));
  5544. });
  5545. // Remove the container
  5546. this.container.remove();
  5547. }
  5548. // Reset document cursor in case it was changed
  5549. document.body.style.cursor = '';
  5550. }
  5551. // Méthode alias pour compatibilité avec le code existant
  5552. close() {
  5553. this.hide();
  5554. }
  5555. }
  5556.  
  5557.  
  5558. ;// ./src/NETWORK/KxsNetwork.ts
  5559.  
  5560. class KxsNetwork {
  5561. sendGlobalChatMessage(text) {
  5562. if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
  5563. return;
  5564. const payload = {
  5565. op: 7,
  5566. d: {
  5567. user: this.getUsername(),
  5568. text
  5569. }
  5570. };
  5571. this.send(payload);
  5572. }
  5573. constructor(kxsClient) {
  5574. this.currentGamePlayers = [];
  5575. this.ws = null;
  5576. this.heartbeatInterval = 0;
  5577. this.isAuthenticated = false;
  5578. this.HOST = config_namespaceObject.api_url;
  5579. this.reconnectAttempts = 0;
  5580. this.maxReconnectAttempts = 3;
  5581. this.reconnectTimeout = 0;
  5582. this.reconnectDelay = 15000; // Initial reconnect delay of 1 second
  5583. this.kxsUsers = 0;
  5584. this.privateUsername = this.generateRandomUsername();
  5585. this.kxsClient = kxsClient;
  5586. }
  5587. connect() {
  5588. if (this.ws && this.ws.readyState === WebSocket.OPEN) {
  5589. this.kxsClient.logger.log('[KxsNetwork] WebSocket already connected');
  5590. return;
  5591. }
  5592. this.ws = new WebSocket(this.getWebSocketURL());
  5593. this.ws.onopen = () => {
  5594. this.kxsClient.logger.log('[KxsNetwork] WebSocket connection established');
  5595. // Reset reconnect attempts on successful connection
  5596. this.reconnectAttempts = 0;
  5597. this.reconnectDelay = 1000;
  5598. };
  5599. this.ws.onmessage = (event) => {
  5600. const data = JSON.parse(event.data);
  5601. this.handleMessage(data);
  5602. };
  5603. this.ws.onerror = (error) => {
  5604. this.kxsClient.nm.showNotification('WebSocket error: ' + error.type, 'error', 900);
  5605. };
  5606. this.ws.onclose = () => {
  5607. this.kxsClient.nm.showNotification('Disconnected from KxsNetwork', 'info', 1100);
  5608. clearInterval(this.heartbeatInterval);
  5609. this.isAuthenticated = false;
  5610. // Try to reconnect
  5611. this.attemptReconnect();
  5612. };
  5613. }
  5614. attemptReconnect() {
  5615. if (this.reconnectAttempts < this.maxReconnectAttempts) {
  5616. this.reconnectAttempts++;
  5617. // Use exponential backoff for reconnection attempts
  5618. const delay = this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1);
  5619. this.kxsClient.logger.log(`[KxsNetwork] Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts}) in ${delay}ms`);
  5620. // Clear any existing timeout
  5621. if (this.reconnectTimeout) {
  5622. clearTimeout(this.reconnectTimeout);
  5623. }
  5624. // Set timeout for reconnection
  5625. this.reconnectTimeout = setTimeout(() => {
  5626. this.connect();
  5627. }, delay);
  5628. }
  5629. else {
  5630. this.kxsClient.logger.log('[KxsNetwork] Maximum reconnection attempts reached');
  5631. this.kxsClient.nm.showNotification('Failed to reconnect after multiple attempts', 'error', 2000);
  5632. }
  5633. }
  5634. generateRandomUsername() {
  5635. let char = 'abcdefghijklmnopqrstuvwxyz0123456789';
  5636. let username = '';
  5637. for (let i = 0; i < 6; i++) {
  5638. username += char[Math.floor(Math.random() * char.length)];
  5639. }
  5640. return "kxs_" + username;
  5641. }
  5642. getUsername() {
  5643. return this.kxsClient.kxsNetworkSettings.nickname_anonymized ? this.privateUsername : JSON.parse(localStorage.getItem("surviv_config") || "{}").playerName;
  5644. }
  5645. identify() {
  5646. const payload = {
  5647. op: 2,
  5648. d: {
  5649. username: this.getUsername(),
  5650. isVoiceChat: this.kxsClient.isVoiceChatEnabled
  5651. }
  5652. };
  5653. this.send(payload);
  5654. }
  5655. handleMessage(_data) {
  5656. const { op, d } = _data;
  5657. switch (op) {
  5658. case 1: //Heart
  5659. {
  5660. if (d === null || d === void 0 ? void 0 : d.count) {
  5661. this.kxsUsers = d.count;
  5662. }
  5663. }
  5664. break;
  5665. case 3: // Kxs user join game
  5666. {
  5667. if (d && Array.isArray(d.players)) {
  5668. const myName = this.getUsername();
  5669. const previousPlayers = this.currentGamePlayers;
  5670. const currentPlayers = d.players.filter((name) => name !== myName);
  5671. // Détecter les nouveaux joueurs (hors soi-même)
  5672. const newPlayers = currentPlayers.filter((name) => !previousPlayers.includes(name));
  5673. for (const newPlayer of newPlayers) {
  5674. this.kxsClient.nm.showNotification(`🎉 ${newPlayer} is a Kxs player!`, 'info', 3500);
  5675. }
  5676. this.currentGamePlayers = currentPlayers;
  5677. }
  5678. }
  5679. break;
  5680. case 7: // Global chat message
  5681. {
  5682. if (d && d.user && d.text) {
  5683. this.kxsClient.chat.addChatMessage(d.user, d.text);
  5684. }
  5685. }
  5686. break;
  5687. case 10: // Hello
  5688. {
  5689. const { heartbeat_interval } = d;
  5690. this.startHeartbeat(heartbeat_interval);
  5691. this.identify();
  5692. }
  5693. break;
  5694. case 2: // Dispatch
  5695. {
  5696. if (d === null || d === void 0 ? void 0 : d.uuid) {
  5697. this.isAuthenticated = true;
  5698. }
  5699. }
  5700. break;
  5701. case 98: // VOICE CHAT UPDATE
  5702. {
  5703. if (d && !d.isVoiceChat && d.user) {
  5704. this.kxsClient.voiceChat.removeUserFromVoice(d.user);
  5705. }
  5706. }
  5707. break;
  5708. }
  5709. }
  5710. startHeartbeat(interval) {
  5711. // Clear existing interval if it exists
  5712. if (this.heartbeatInterval) {
  5713. clearInterval(this.heartbeatInterval);
  5714. }
  5715. this.heartbeatInterval = setInterval(() => {
  5716. this.send({
  5717. op: 1,
  5718. d: {}
  5719. });
  5720. }, interval);
  5721. }
  5722. send(data) {
  5723. var _a;
  5724. if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
  5725. this.ws.send(JSON.stringify(data));
  5726. }
  5727. }
  5728. disconnect() {
  5729. if (this.ws) {
  5730. // Clear all timers
  5731. clearInterval(this.heartbeatInterval);
  5732. clearTimeout(this.reconnectTimeout);
  5733. // Reset reconnection state
  5734. this.reconnectAttempts = 0;
  5735. // Close the connection
  5736. this.ws.close();
  5737. }
  5738. }
  5739. reconnect() {
  5740. this.disconnect();
  5741. this.connect();
  5742. }
  5743. sendGameInfoToWebSocket(gameId) {
  5744. if (!this.isAuthenticated || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
  5745. return;
  5746. }
  5747. try {
  5748. const payload = {
  5749. op: 3, // Custom operation code for game info
  5750. d: {
  5751. type: 'find_game_response',
  5752. gameId
  5753. }
  5754. };
  5755. this.send(payload);
  5756. }
  5757. catch (error) {
  5758. }
  5759. }
  5760. getWebSocketURL() {
  5761. let isSecured = this.HOST.startsWith("https://");
  5762. let protocols = isSecured ? "wss://" : "ws://";
  5763. return protocols + this.HOST.split("/")[2];
  5764. }
  5765. getHTTPURL() {
  5766. return this.HOST;
  5767. }
  5768. getOnlineCount() {
  5769. return this.kxsUsers;
  5770. }
  5771. gameEnded() {
  5772. var _a;
  5773. (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify({ op: 4, d: {} }));
  5774. }
  5775. }
  5776.  
  5777.  
  5778. ;// ./src/UTILS/KxsChat.ts
  5779. class KxsChat {
  5780. constructor(kxsClient) {
  5781. this.chatInput = null;
  5782. this.chatBox = null;
  5783. this.messagesContainer = null;
  5784. this.chatMessages = [];
  5785. this.chatOpen = false;
  5786. this.handleKeyDown = (e) => {
  5787. if (e.key === 'Enter' && !this.chatOpen && document.activeElement !== this.chatInput) {
  5788. e.preventDefault();
  5789. this.openChatInput();
  5790. }
  5791. else if (e.key === 'Escape' && this.chatOpen) {
  5792. this.closeChatInput();
  5793. }
  5794. };
  5795. this.kxsClient = kxsClient;
  5796. this.initGlobalChat();
  5797. // Initialize chat visibility based on the current setting
  5798. if (this.chatBox && !this.kxsClient.isKxsChatEnabled) {
  5799. this.chatBox.style.display = 'none';
  5800. window.removeEventListener('keydown', this.handleKeyDown);
  5801. }
  5802. }
  5803. initGlobalChat() {
  5804. const area = document.getElementById('game-touch-area');
  5805. if (!area)
  5806. return;
  5807. // Chat box
  5808. const chatBox = document.createElement('div');
  5809. chatBox.id = 'kxs-chat-box';
  5810. // Messages container
  5811. const messagesContainer = document.createElement('div');
  5812. messagesContainer.id = 'kxs-chat-messages';
  5813. messagesContainer.style.display = 'flex';
  5814. messagesContainer.style.flexDirection = 'column';
  5815. messagesContainer.style.gap = '3px';
  5816. chatBox.appendChild(messagesContainer);
  5817. this.messagesContainer = messagesContainer;
  5818. chatBox.style.position = 'absolute';
  5819. chatBox.style.left = '50%';
  5820. chatBox.style.bottom = '38px';
  5821. chatBox.style.transform = 'translateX(-50%)';
  5822. chatBox.style.minWidth = '260px';
  5823. chatBox.style.maxWidth = '480px';
  5824. chatBox.style.background = 'rgba(30,30,40,0.80)';
  5825. chatBox.style.color = '#fff';
  5826. chatBox.style.borderRadius = '10px';
  5827. chatBox.style.padding = '7px 14px 4px 14px';
  5828. chatBox.style.fontSize = '15px';
  5829. chatBox.style.fontFamily = 'inherit';
  5830. chatBox.style.zIndex = '1002';
  5831. chatBox.style.pointerEvents = 'auto';
  5832. chatBox.style.cursor = 'move'; // Indique que c'est déplaçable
  5833. chatBox.style.display = 'flex';
  5834. chatBox.style.flexDirection = 'column';
  5835. chatBox.style.gap = '3px';
  5836. chatBox.style.opacity = '0.5';
  5837. // Charger la position sauvegardée dès l'initialisation
  5838. const savedPosition = localStorage.getItem('kxs-chat-box-position');
  5839. if (savedPosition) {
  5840. try {
  5841. const { x, y } = JSON.parse(savedPosition);
  5842. chatBox.style.left = `${x}px`;
  5843. chatBox.style.top = `${y}px`;
  5844. chatBox.style.position = 'absolute';
  5845. }
  5846. catch (e) { }
  5847. }
  5848. area.appendChild(chatBox);
  5849. this.chatBox = chatBox;
  5850. // Rendre la chatbox draggable UNIQUEMENT si le menu secondaire est ouvert
  5851. const updateChatDraggable = () => {
  5852. const isMenuOpen = this.kxsClient.secondaryMenu.getMenuVisibility();
  5853. if (isMenuOpen) {
  5854. chatBox.style.pointerEvents = 'auto';
  5855. chatBox.style.cursor = 'move';
  5856. this.kxsClient.makeDraggable(chatBox, 'kxs-chat-box-position');
  5857. }
  5858. else {
  5859. chatBox.style.pointerEvents = 'none';
  5860. chatBox.style.cursor = 'default';
  5861. }
  5862. };
  5863. // Initial state
  5864. updateChatDraggable();
  5865. // Observe menu changes
  5866. const observer = new MutationObserver(updateChatDraggable);
  5867. if (this.kxsClient.secondaryMenu && this.kxsClient.secondaryMenu.menu) {
  5868. observer.observe(this.kxsClient.secondaryMenu.menu, { attributes: true, attributeFilter: ['style', 'class'] });
  5869. }
  5870. // Optionnel : timer pour fallback (si le menu est modifié autrement)
  5871. setInterval(updateChatDraggable, 500);
  5872. // Input
  5873. const input = document.createElement('input');
  5874. input.type = 'text';
  5875. input.placeholder = 'Press Enter to write...';
  5876. input.id = 'kxs-chat-input';
  5877. input.style.width = '100%';
  5878. input.style.boxSizing = 'border-box';
  5879. input.style.padding = '8px 12px';
  5880. input.style.borderRadius = '8px';
  5881. input.style.border = 'none';
  5882. input.style.background = 'rgba(40,40,50,0.95)';
  5883. input.style.color = '#fff';
  5884. input.style.fontSize = '15px';
  5885. input.style.fontFamily = 'inherit';
  5886. input.style.zIndex = '1003';
  5887. input.style.outline = 'none';
  5888. input.style.display = this.chatOpen ? 'block' : 'none';
  5889. input.style.opacity = '0.5';
  5890. input.style.marginTop = 'auto'; // Pour coller l'input en bas
  5891. chatBox.appendChild(input); // Ajoute l'input dans la chatBox
  5892. this.chatInput = input;
  5893. // Ajuste le style de chatBox pour le layout
  5894. chatBox.style.display = 'flex';
  5895. chatBox.style.flexDirection = 'column';
  5896. chatBox.style.gap = '3px';
  5897. chatBox.style.justifyContent = 'flex-end'; // S'assure que l'input est en bas
  5898. // Focus automatique sur l'input quand on clique dessus ou sur la chatBox
  5899. input.addEventListener('focus', () => {
  5900. // Rien de spécial, mais peut servir à customiser plus tard
  5901. });
  5902. chatBox.addEventListener('mousedown', (e) => {
  5903. // Focus l'input si clic sur la chatBox (hors drag)
  5904. if (e.target === chatBox) {
  5905. input.focus();
  5906. }
  5907. });
  5908. input.addEventListener('mousedown', () => {
  5909. input.focus();
  5910. });
  5911. ['keydown', 'keypress', 'keyup'].forEach(eventType => {
  5912. input.addEventListener(eventType, (e) => {
  5913. const ke = e;
  5914. if (eventType === 'keydown') {
  5915. if (ke.key === 'Enter') {
  5916. const txt = input.value.trim();
  5917. if (txt)
  5918. this.kxsClient.kxsNetwork.sendGlobalChatMessage(txt);
  5919. input.value = '';
  5920. this.closeChatInput();
  5921. }
  5922. else if (ke.key === 'Escape') {
  5923. this.closeChatInput();
  5924. }
  5925. }
  5926. e.stopImmediatePropagation();
  5927. e.stopPropagation();
  5928. }, true);
  5929. });
  5930. // Gestion clavier
  5931. window.addEventListener('keydown', this.handleKeyDown);
  5932. }
  5933. openChatInput() {
  5934. if (!this.chatInput)
  5935. return;
  5936. this.chatInput.placeholder = 'Press Enter to write...';
  5937. this.chatInput.value = '';
  5938. this.chatInput.style.display = 'block';
  5939. this.chatInput.focus();
  5940. this.chatOpen = true;
  5941. }
  5942. closeChatInput() {
  5943. if (!this.chatInput)
  5944. return;
  5945. this.chatInput.style.display = 'none';
  5946. this.chatInput.blur();
  5947. this.chatOpen = false;
  5948. }
  5949. addChatMessage(user, text) {
  5950. if (!this.chatBox || !this.kxsClient.isKxsChatEnabled)
  5951. return;
  5952. this.chatMessages.push({ user, text });
  5953. if (this.chatMessages.length > 5)
  5954. this.chatMessages.shift();
  5955. if (this.messagesContainer) {
  5956. this.messagesContainer.innerHTML = this.chatMessages.map(m => `<span><b style='color:#3fae2a;'>${m.user}</b>: ${m.text}</span>`).join('');
  5957. }
  5958. }
  5959. toggleChat() {
  5960. if (this.chatBox) {
  5961. this.chatBox.style.display = this.kxsClient.isKxsChatEnabled ? 'flex' : 'none';
  5962. }
  5963. if (this.kxsClient.isKxsChatEnabled) {
  5964. window.addEventListener('keydown', this.handleKeyDown);
  5965. }
  5966. else {
  5967. this.closeChatInput();
  5968. window.removeEventListener('keydown', this.handleKeyDown);
  5969. }
  5970. const message = this.kxsClient.isKxsChatEnabled ? 'Chat enabled' : 'Chat disabled';
  5971. const type = this.kxsClient.isKxsChatEnabled ? 'success' : 'info';
  5972. this.kxsClient.nm.showNotification(message, type, 600);
  5973. }
  5974. }
  5975.  
  5976.  
  5977. ;// ./src/UTILS/KxsVoiceChat.ts
  5978. var KxsVoiceChat_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
  5979. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  5980. return new (P || (P = Promise))(function (resolve, reject) {
  5981. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  5982. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  5983. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  5984. step((generator = generator.apply(thisArg, _arguments || [])).next());
  5985. });
  5986. };
  5987. class KxsVoiceChat {
  5988. constructor(kxsClient, kxsNetwork) {
  5989. this.audioCtx = null;
  5990. this.micStream = null;
  5991. this.micSource = null;
  5992. this.processor = null;
  5993. // Overlay elements
  5994. this.overlayContainer = null;
  5995. this.activeUsers = new Map();
  5996. this.mutedUsers = new Set();
  5997. this.activityCheckInterval = null;
  5998. this.isOverlayVisible = true;
  5999. this.isLocalMuted = false;
  6000. this.localMuteButton = null;
  6001. // Constants
  6002. this.ACTIVITY_THRESHOLD = 0.01;
  6003. this.INACTIVITY_TIMEOUT = 2000;
  6004. this.REMOVAL_TIMEOUT = 30000;
  6005. this.ACTIVITY_CHECK_INTERVAL = 500;
  6006. this.kxsClient = kxsClient;
  6007. this.kxsNetwork = kxsNetwork;
  6008. this.createOverlayContainer();
  6009. }
  6010. /**
  6011. * Remove a user from voice chat (e.g., when muted)
  6012. */
  6013. removeUserFromVoice(username) {
  6014. if (this.activeUsers.has(username)) {
  6015. this.activeUsers.delete(username);
  6016. this.updateOverlayUI();
  6017. }
  6018. }
  6019. startVoiceChat() {
  6020. return KxsVoiceChat_awaiter(this, void 0, void 0, function* () {
  6021. if (!this.kxsClient.isVoiceChatEnabled)
  6022. return;
  6023. this.cleanup();
  6024. this.showOverlay();
  6025. try {
  6026. this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  6027. this.micStream = yield navigator.mediaDevices.getUserMedia({
  6028. audio: {
  6029. sampleRate: 48000,
  6030. channelCount: 1,
  6031. echoCancellation: true,
  6032. noiseSuppression: true,
  6033. autoGainControl: true
  6034. }
  6035. });
  6036. this.micSource = this.audioCtx.createMediaStreamSource(this.micStream);
  6037. this.processor = this.audioCtx.createScriptProcessor(2048, 1, 1);
  6038. this.micSource.connect(this.processor);
  6039. this.processor.connect(this.audioCtx.destination);
  6040. // Set up audio processing
  6041. this.setupAudioProcessing();
  6042. this.setupWebSocketListeners();
  6043. }
  6044. catch (error) {
  6045. alert("Unable to initialize voice chat: " + error.message);
  6046. this.cleanup();
  6047. }
  6048. });
  6049. }
  6050. setupAudioProcessing() {
  6051. if (!this.processor)
  6052. return;
  6053. this.processor.onaudioprocess = (e) => {
  6054. if (!this.kxsNetwork.ws || this.kxsNetwork.ws.readyState !== WebSocket.OPEN)
  6055. return;
  6056. // Ne pas envoyer les données audio si l'utilisateur local est muté
  6057. if (this.isLocalMuted)
  6058. return;
  6059. const input = e.inputBuffer.getChannelData(0);
  6060. const int16 = new Int16Array(input.length);
  6061. for (let i = 0; i < input.length; i++) {
  6062. int16[i] = Math.max(-32768, Math.min(32767, input[i] * 32767));
  6063. }
  6064. this.kxsNetwork.ws.send(JSON.stringify({ op: 99, d: Array.from(int16) }));
  6065. };
  6066. }
  6067. setupWebSocketListeners() {
  6068. if (!this.kxsNetwork.ws)
  6069. return;
  6070. this.kxsNetwork.ws.addEventListener('message', this.handleAudioMessage.bind(this));
  6071. }
  6072. handleAudioMessage(msg) {
  6073. let parsed;
  6074. try {
  6075. parsed = typeof msg.data === 'string' ? JSON.parse(msg.data) : msg.data;
  6076. }
  6077. catch (_a) {
  6078. return;
  6079. }
  6080. if (!parsed || parsed.op !== 99 || !parsed.d || !parsed.u)
  6081. return;
  6082. try {
  6083. // Skip if user is muted
  6084. if (this.mutedUsers.has(parsed.u))
  6085. return;
  6086. const int16Data = new Int16Array(parsed.d);
  6087. const floatData = new Float32Array(int16Data.length);
  6088. // Calculate audio level for visualization
  6089. let audioLevel = 0;
  6090. for (let i = 0; i < int16Data.length; i++) {
  6091. floatData[i] = int16Data[i] / 32767;
  6092. audioLevel += floatData[i] * floatData[i];
  6093. }
  6094. audioLevel = Math.sqrt(audioLevel / int16Data.length);
  6095. // Update user activity in the overlay
  6096. this.updateUserActivity(parsed.u, audioLevel);
  6097. // Play the audio
  6098. this.playAudio(floatData);
  6099. }
  6100. catch (error) {
  6101. console.error("Audio processing error:", error);
  6102. }
  6103. }
  6104. playAudio(floatData) {
  6105. if (!this.audioCtx)
  6106. return;
  6107. const buffer = this.audioCtx.createBuffer(1, floatData.length, this.audioCtx.sampleRate);
  6108. buffer.getChannelData(0).set(floatData);
  6109. const source = this.audioCtx.createBufferSource();
  6110. source.buffer = buffer;
  6111. source.connect(this.audioCtx.destination);
  6112. source.start();
  6113. }
  6114. stopVoiceChat() {
  6115. this.cleanup();
  6116. this.hideOverlay();
  6117. }
  6118. cleanup() {
  6119. if (this.processor) {
  6120. this.processor.disconnect();
  6121. this.processor = null;
  6122. }
  6123. if (this.micSource) {
  6124. this.micSource.disconnect();
  6125. this.micSource = null;
  6126. }
  6127. if (this.micStream) {
  6128. this.micStream.getTracks().forEach(track => track.stop());
  6129. this.micStream = null;
  6130. }
  6131. if (this.audioCtx) {
  6132. this.audioCtx.close();
  6133. this.audioCtx = null;
  6134. }
  6135. if (this.activityCheckInterval) {
  6136. window.clearInterval(this.activityCheckInterval);
  6137. this.activityCheckInterval = null;
  6138. }
  6139. }
  6140. toggleVoiceChat() {
  6141. var _a, _b;
  6142. if (this.kxsClient.isVoiceChatEnabled) {
  6143. (_a = this.kxsNetwork.ws) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify({
  6144. op: 98,
  6145. d: { isVoiceChat: true }
  6146. }));
  6147. this.startVoiceChat();
  6148. }
  6149. else {
  6150. this.stopVoiceChat();
  6151. (_b = this.kxsNetwork.ws) === null || _b === void 0 ? void 0 : _b.send(JSON.stringify({
  6152. op: 98,
  6153. d: { isVoiceChat: false }
  6154. }));
  6155. }
  6156. }
  6157. createOverlayContainer() {
  6158. if (this.overlayContainer)
  6159. return;
  6160. this.overlayContainer = document.createElement('div');
  6161. this.overlayContainer.id = 'kxs-voice-chat-overlay';
  6162. Object.assign(this.overlayContainer.style, {
  6163. position: 'absolute',
  6164. top: '10px',
  6165. right: '10px',
  6166. width: '200px',
  6167. backgroundColor: 'rgba(0, 0, 0, 0.7)',
  6168. color: 'white',
  6169. padding: '10px',
  6170. borderRadius: '5px',
  6171. zIndex: '1000',
  6172. fontFamily: 'Arial, sans-serif',
  6173. fontSize: '14px',
  6174. display: 'none',
  6175. cursor: 'move'
  6176. });
  6177. // Charger la position sauvegardée si elle existe
  6178. const savedPosition = localStorage.getItem('kxs-voice-chat-position');
  6179. if (savedPosition) {
  6180. try {
  6181. const { x, y } = JSON.parse(savedPosition);
  6182. this.overlayContainer.style.left = `${x}px`;
  6183. this.overlayContainer.style.top = `${y}px`;
  6184. this.overlayContainer.style.right = 'auto';
  6185. }
  6186. catch (e) { }
  6187. }
  6188. // Add title and controls container (for title and mute button)
  6189. const controlsContainer = document.createElement('div');
  6190. Object.assign(controlsContainer.style, {
  6191. display: 'flex',
  6192. justifyContent: 'space-between',
  6193. alignItems: 'center',
  6194. marginBottom: '5px',
  6195. borderBottom: '1px solid rgba(255, 255, 255, 0.3)',
  6196. paddingBottom: '5px'
  6197. });
  6198. // Title
  6199. const title = document.createElement('div');
  6200. title.textContent = 'Voice Chat';
  6201. Object.assign(title.style, {
  6202. fontWeight: 'bold'
  6203. });
  6204. // Fonction pour mettre à jour l'état draggable selon la visibilité du menu RSHIFT
  6205. const updateVoiceChatDraggable = () => {
  6206. const isMenuOpen = this.kxsClient.secondaryMenu.getMenuVisibility();
  6207. if (isMenuOpen) {
  6208. this.overlayContainer.style.pointerEvents = 'auto';
  6209. this.overlayContainer.style.cursor = 'move';
  6210. this.kxsClient.makeDraggable(this.overlayContainer, 'kxs-voice-chat-position');
  6211. }
  6212. else {
  6213. this.overlayContainer.style.pointerEvents = 'none';
  6214. this.overlayContainer.style.cursor = 'default';
  6215. }
  6216. };
  6217. // Initial state
  6218. updateVoiceChatDraggable();
  6219. // Observer les changements du menu
  6220. const observer = new MutationObserver(updateVoiceChatDraggable);
  6221. if (this.kxsClient.secondaryMenu && this.kxsClient.secondaryMenu.menu) {
  6222. observer.observe(this.kxsClient.secondaryMenu.menu, { attributes: true, attributeFilter: ['style', 'class'] });
  6223. }
  6224. // Fallback timer pour s'assurer de l'état correct
  6225. setInterval(updateVoiceChatDraggable, 500);
  6226. // Create local mute button
  6227. this.localMuteButton = this.createLocalMuteButton();
  6228. // Add elements to controls container
  6229. controlsContainer.appendChild(title);
  6230. controlsContainer.appendChild(this.localMuteButton);
  6231. this.overlayContainer.appendChild(controlsContainer);
  6232. // Container for users
  6233. const usersContainer = document.createElement('div');
  6234. usersContainer.id = 'kxs-voice-chat-users';
  6235. this.overlayContainer.appendChild(usersContainer);
  6236. document.body.appendChild(this.overlayContainer);
  6237. this.startActivityCheck();
  6238. }
  6239. showOverlay() {
  6240. if (!this.overlayContainer)
  6241. return;
  6242. this.overlayContainer.style.display = 'block';
  6243. this.isOverlayVisible = true;
  6244. if (!this.activityCheckInterval) {
  6245. this.startActivityCheck();
  6246. }
  6247. }
  6248. hideOverlay() {
  6249. if (!this.overlayContainer)
  6250. return;
  6251. this.overlayContainer.style.display = 'none';
  6252. this.isOverlayVisible = false;
  6253. }
  6254. toggleOverlay() {
  6255. if (this.isOverlayVisible) {
  6256. this.hideOverlay();
  6257. }
  6258. else {
  6259. this.showOverlay();
  6260. }
  6261. return this.isOverlayVisible;
  6262. }
  6263. updateUserActivity(username, audioLevel) {
  6264. const now = Date.now();
  6265. const isActive = audioLevel > this.ACTIVITY_THRESHOLD;
  6266. let user = this.activeUsers.get(username);
  6267. if (!user) {
  6268. user = {
  6269. username,
  6270. isActive,
  6271. lastActivity: now,
  6272. audioLevel,
  6273. isMuted: this.mutedUsers.has(username)
  6274. };
  6275. this.activeUsers.set(username, user);
  6276. }
  6277. else {
  6278. user.isActive = isActive;
  6279. user.lastActivity = now;
  6280. user.audioLevel = audioLevel;
  6281. user.isMuted = this.mutedUsers.has(username);
  6282. }
  6283. this.updateOverlayUI();
  6284. }
  6285. startActivityCheck() {
  6286. this.activityCheckInterval = window.setInterval(() => {
  6287. const now = Date.now();
  6288. let updated = false;
  6289. this.activeUsers.forEach((user, username) => {
  6290. // Set inactive if no activity for the specified timeout
  6291. if (now - user.lastActivity > this.INACTIVITY_TIMEOUT && user.isActive) {
  6292. user.isActive = false;
  6293. updated = true;
  6294. }
  6295. // Remove users inactive for longer period
  6296. if (now - user.lastActivity > this.REMOVAL_TIMEOUT) {
  6297. this.activeUsers.delete(username);
  6298. updated = true;
  6299. }
  6300. });
  6301. if (updated) {
  6302. this.updateOverlayUI();
  6303. }
  6304. }, this.ACTIVITY_CHECK_INTERVAL);
  6305. }
  6306. updateOverlayUI() {
  6307. if (!this.overlayContainer || !this.isOverlayVisible)
  6308. return;
  6309. const usersContainer = document.getElementById('kxs-voice-chat-users');
  6310. if (!usersContainer)
  6311. return;
  6312. // Clear existing users
  6313. usersContainer.innerHTML = '';
  6314. // Add users or show "no users" message
  6315. if (this.activeUsers.size === 0) {
  6316. this.renderNoUsersMessage(usersContainer);
  6317. }
  6318. else {
  6319. this.activeUsers.forEach(user => {
  6320. this.renderUserElement(usersContainer, user);
  6321. });
  6322. }
  6323. }
  6324. renderNoUsersMessage(container) {
  6325. const noUsers = document.createElement('div');
  6326. noUsers.textContent = 'No active users';
  6327. Object.assign(noUsers.style, {
  6328. color: 'rgba(255, 255, 255, 0.6)',
  6329. fontStyle: 'italic',
  6330. textAlign: 'center',
  6331. padding: '5px'
  6332. });
  6333. container.appendChild(noUsers);
  6334. }
  6335. renderUserElement(container, user) {
  6336. const userElement = document.createElement('div');
  6337. userElement.className = 'kxs-voice-chat-user';
  6338. Object.assign(userElement.style, {
  6339. display: 'flex',
  6340. alignItems: 'center',
  6341. margin: '3px 0',
  6342. padding: '3px',
  6343. borderRadius: '3px',
  6344. backgroundColor: 'rgba(255, 255, 255, 0.1)'
  6345. });
  6346. // Status indicator
  6347. const indicator = this.createStatusIndicator(user);
  6348. // Username label
  6349. const usernameLabel = document.createElement('span');
  6350. usernameLabel.textContent = user.username;
  6351. Object.assign(usernameLabel.style, {
  6352. flexGrow: '1',
  6353. whiteSpace: 'nowrap',
  6354. overflow: 'hidden',
  6355. textOverflow: 'ellipsis'
  6356. });
  6357. // Mute button - FIX: Create this element properly
  6358. const muteButton = this.createMuteButton(user);
  6359. // Add elements to container
  6360. userElement.appendChild(indicator);
  6361. userElement.appendChild(usernameLabel);
  6362. userElement.appendChild(muteButton);
  6363. container.appendChild(userElement);
  6364. }
  6365. createStatusIndicator(user) {
  6366. const indicator = document.createElement('div');
  6367. Object.assign(indicator.style, {
  6368. width: '14px',
  6369. height: '14px',
  6370. borderRadius: '50%',
  6371. marginRight: '8px',
  6372. cursor: 'pointer'
  6373. });
  6374. indicator.title = user.isMuted ? 'Unmute' : 'Mute';
  6375. if (user.isActive) {
  6376. const scale = 1 + Math.min(user.audioLevel * 3, 1);
  6377. Object.assign(indicator.style, {
  6378. backgroundColor: '#2ecc71',
  6379. transform: `scale(${scale})`,
  6380. boxShadow: '0 0 5px #2ecc71',
  6381. transition: 'transform 0.1s ease-in-out'
  6382. });
  6383. }
  6384. else {
  6385. indicator.style.backgroundColor = '#7f8c8d';
  6386. }
  6387. return indicator;
  6388. }
  6389. createMuteButton(user) {
  6390. const muteButton = document.createElement('button');
  6391. muteButton.type = 'button'; // Important: specify type to prevent form submission behavior
  6392. muteButton.textContent = user.isMuted ? 'UNMUTE' : 'MUTE';
  6393. Object.assign(muteButton.style, {
  6394. backgroundColor: user.isMuted ? '#e74c3c' : '#7f8c8d',
  6395. color: 'white',
  6396. border: 'none',
  6397. borderRadius: '3px',
  6398. padding: '2px 5px',
  6399. marginLeft: '5px',
  6400. cursor: 'pointer',
  6401. fontSize: '11px',
  6402. fontWeight: 'bold',
  6403. minWidth: '40px'
  6404. });
  6405. muteButton.addEventListener('mouseover', () => {
  6406. muteButton.style.opacity = '0.8';
  6407. });
  6408. muteButton.addEventListener('mouseout', () => {
  6409. muteButton.style.opacity = '1';
  6410. });
  6411. const handleMuteToggle = (e) => {
  6412. e.stopImmediatePropagation();
  6413. e.stopPropagation();
  6414. e.preventDefault();
  6415. const newMutedState = !user.isMuted;
  6416. user.isMuted = newMutedState;
  6417. if (newMutedState) {
  6418. this.mutedUsers.add(user.username);
  6419. }
  6420. else {
  6421. this.mutedUsers.delete(user.username);
  6422. }
  6423. this.sendMuteState(user.username, newMutedState);
  6424. this.updateOverlayUI();
  6425. return false;
  6426. };
  6427. ['click', 'mousedown', 'pointerdown'].forEach(eventType => {
  6428. muteButton.addEventListener(eventType, handleMuteToggle, true);
  6429. });
  6430. muteButton.onclick = (e) => {
  6431. e.stopImmediatePropagation();
  6432. e.stopPropagation();
  6433. e.preventDefault();
  6434. const newMutedState = !user.isMuted;
  6435. user.isMuted = newMutedState;
  6436. if (newMutedState) {
  6437. this.mutedUsers.add(user.username);
  6438. }
  6439. else {
  6440. this.mutedUsers.delete(user.username);
  6441. }
  6442. this.sendMuteState(user.username, newMutedState);
  6443. this.updateOverlayUI();
  6444. return false;
  6445. };
  6446. return muteButton;
  6447. }
  6448. sendMuteState(username, isMuted) {
  6449. if (!this.kxsNetwork.ws || this.kxsNetwork.ws.readyState !== WebSocket.OPEN) {
  6450. return;
  6451. }
  6452. this.kxsNetwork.ws.send(JSON.stringify({
  6453. op: 100,
  6454. d: {
  6455. user: username,
  6456. isMuted: isMuted
  6457. }
  6458. }));
  6459. }
  6460. createLocalMuteButton() {
  6461. const muteButton = document.createElement('button');
  6462. muteButton.type = 'button';
  6463. muteButton.textContent = this.isLocalMuted ? 'UNMUTE' : 'MUTE';
  6464. muteButton.id = 'kxs-voice-chat-local-mute';
  6465. Object.assign(muteButton.style, {
  6466. backgroundColor: this.isLocalMuted ? '#e74c3c' : '#3498db',
  6467. color: 'white',
  6468. border: 'none',
  6469. borderRadius: '3px',
  6470. padding: '2px 5px',
  6471. cursor: 'pointer',
  6472. fontSize: '11px',
  6473. fontWeight: 'bold',
  6474. minWidth: '55px'
  6475. });
  6476. muteButton.addEventListener('mouseover', () => {
  6477. muteButton.style.opacity = '0.8';
  6478. });
  6479. muteButton.addEventListener('mouseout', () => {
  6480. muteButton.style.opacity = '1';
  6481. });
  6482. // Utiliser un gestionnaire d'événement unique plus simple avec une vérification pour éviter les multiples déclenchements
  6483. muteButton.onclick = (e) => {
  6484. // Arrêter complètement la propagation de l'événement
  6485. e.stopImmediatePropagation();
  6486. e.stopPropagation();
  6487. e.preventDefault();
  6488. // Basculer l'état de mute
  6489. this.toggleLocalMute();
  6490. return false;
  6491. };
  6492. return muteButton;
  6493. }
  6494. toggleLocalMute() {
  6495. // Inverser l'état
  6496. this.isLocalMuted = !this.isLocalMuted;
  6497. // Mettre à jour l'apparence du bouton si présent
  6498. if (this.localMuteButton) {
  6499. // Définir clairement le texte et la couleur du bouton en fonction de l'état
  6500. this.localMuteButton.textContent = this.isLocalMuted ? 'UNMUTE' : 'MUTE';
  6501. this.localMuteButton.style.backgroundColor = this.isLocalMuted ? '#e74c3c' : '#3498db';
  6502. }
  6503. // Type de notification en fonction de si nous sommes sur error, info ou success
  6504. const notificationType = this.isLocalMuted ? 'error' : 'success';
  6505. // Notification de changement d'état
  6506. const message = this.isLocalMuted ? 'You are muted' : 'You are unmuted';
  6507. this.kxsClient.nm.showNotification(message, notificationType, 2000);
  6508. }
  6509. }
  6510.  
  6511.  
  6512. ;// ./src/KxsClient.ts
  6513. var KxsClient_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
  6514. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  6515. return new (P || (P = Promise))(function (resolve, reject) {
  6516. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  6517. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  6518. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  6519. step((generator = generator.apply(thisArg, _arguments || [])).next());
  6520. });
  6521. };
  6522.  
  6523.  
  6524.  
  6525.  
  6526.  
  6527.  
  6528.  
  6529.  
  6530.  
  6531.  
  6532.  
  6533.  
  6534.  
  6535.  
  6536.  
  6537.  
  6538.  
  6539.  
  6540. class KxsClient {
  6541. constructor() {
  6542. this.onlineMenuElement = null;
  6543. this.onlineMenuInterval = null;
  6544. this.deathObserver = null;
  6545. this.adBlockObserver = null;
  6546. globalThis.kxsClient = this;
  6547. this.logger = new Logger();
  6548. this.config = config_namespaceObject;
  6549. this.menu = document.createElement("div");
  6550. this.lastFrameTime = performance.now();
  6551. this.isFpsUncapped = false;
  6552. this.isFpsVisible = true;
  6553. this.isPingVisible = true;
  6554. this.isKillsVisible = true;
  6555. this.isDeathSoundEnabled = true;
  6556. this.isWinSoundEnabled = true;
  6557. this.isHealthWarningEnabled = true;
  6558. this.isAutoUpdateEnabled = true;
  6559. this.isWinningAnimationEnabled = true;
  6560. this.isKillLeaderTrackerEnabled = true;
  6561. this.isKillFeedBlint = false;
  6562. this.isSpotifyPlayerEnabled = false;
  6563. this.discordToken = null;
  6564. this.counters = {};
  6565. this.all_friends = '';
  6566. this.isMainMenuCleaned = false;
  6567. this.isNotifyingForToggleMenu = true;
  6568. this.isGunOverlayColored = true;
  6569. this.customCrosshair = null;
  6570. this.isGunBorderChromatic = false;
  6571. this.isKxsChatEnabled = true;
  6572. this.isVoiceChatEnabled = false;
  6573. this.isFocusModeEnabled = false;
  6574. this.isHealBarIndicatorEnabled = true;
  6575. this.defaultPositions = {
  6576. fps: { left: 20, top: 160 },
  6577. ping: { left: 20, top: 220 },
  6578. kills: { left: 20, top: 280 },
  6579. lowHpWarning: { left: 285, top: 742 },
  6580. };
  6581. this.defaultSizes = {
  6582. fps: { width: 100, height: 30 },
  6583. ping: { width: 100, height: 30 },
  6584. kills: { width: 100, height: 30 },
  6585. };
  6586. this.kxsNetworkSettings = {
  6587. nickname_anonymized: false,
  6588. };
  6589. this.soundLibrary = {
  6590. win_sound_url: win_sound,
  6591. death_sound_url: death_sound,
  6592. background_sound_url: background_song,
  6593. };
  6594. this.gridSystem = new GridSystem();
  6595. this.db = new browser/* SteganoDB */.w({ database: "KxsClient", tableName: "gameplay_history" });
  6596. // Before all, load local storage
  6597. this.loadLocalStorage();
  6598. this.changeSurvevLogo();
  6599. this.nm = NotificationManager.getInstance();
  6600. this.discordRPC = new DiscordWebSocket(this, this.parseToken(this.discordToken));
  6601. this.updater = new UpdateChecker(this);
  6602. this.kill_leader = new KillLeaderTracker(this);
  6603. this.healWarning = new HealthWarning(this);
  6604. this.historyManager = new GameHistoryMenu(this);
  6605. this.kxsNetwork = new KxsNetwork(this);
  6606. this.setAnimationFrameCallback();
  6607. this.loadBackgroundFromLocalStorage();
  6608. this.initDeathDetection();
  6609. this.discordRPC.connect();
  6610. this.hud = new KxsClientHUD(this);
  6611. this.secondaryMenu = new KxsClientSecondaryMenu(this);
  6612. this.discordTracker = new DiscordTracking(this, this.discordWebhookUrl);
  6613. this.chat = new KxsChat(this);
  6614. this.voiceChat = new KxsVoiceChat(this, this.kxsNetwork);
  6615. if (this.isSpotifyPlayerEnabled) {
  6616. this.createSimpleSpotifyPlayer();
  6617. }
  6618. this.MainMenuCleaning();
  6619. this.kxsNetwork.connect();
  6620. this.createOnlineMenu();
  6621. this.voiceChat.startVoiceChat();
  6622. }
  6623. parseToken(token) {
  6624. if (token) {
  6625. return token.replace(/^(["'`])(.+)\1$/, '$2');
  6626. }
  6627. return null;
  6628. }
  6629. getPlayerName() {
  6630. let config = localStorage.getItem("surviv_config");
  6631. if (config) {
  6632. let configObject = JSON.parse(config);
  6633. return configObject.playerName;
  6634. }
  6635. }
  6636. changeSurvevLogo() {
  6637. var startRowHeader = document.querySelector("#start-row-header");
  6638. if (startRowHeader) {
  6639. startRowHeader.style.backgroundImage =
  6640. `url("${full_logo}")`;
  6641. }
  6642. }
  6643. createOnlineMenu() {
  6644. // Cherche le div #start-overlay
  6645. const overlay = document.getElementById('start-overlay');
  6646. if (!overlay)
  6647. return;
  6648. // Crée le menu
  6649. const menu = document.createElement('div');
  6650. menu.id = 'kxs-online-menu';
  6651. menu.style.position = 'absolute';
  6652. menu.style.top = '18px';
  6653. menu.style.right = '18px';
  6654. menu.style.background = 'rgba(30,30,40,0.92)';
  6655. menu.style.color = '#fff';
  6656. menu.style.padding = '8px 18px';
  6657. menu.style.borderRadius = '12px';
  6658. menu.style.boxShadow = '0 2px 8px rgba(0,0,0,0.18)';
  6659. menu.style.fontSize = '15px';
  6660. menu.style.zIndex = '999';
  6661. menu.style.userSelect = 'none';
  6662. menu.style.pointerEvents = 'auto';
  6663. menu.style.fontFamily = 'inherit';
  6664. menu.style.display = 'flex';
  6665. menu.style.alignItems = 'center';
  6666. menu.innerHTML = `
  6667. <span id="kxs-online-dot" style="display:inline-block;width:12px;height:12px;border-radius:50%;background:#3fae2a;margin-right:10px;box-shadow:0 0 8px #3fae2a;animation:kxs-pulse 1s infinite alternate;"></span>
  6668. <b></b> <span id="kxs-online-count">...</span>
  6669. `;
  6670. // Ajoute l'animation CSS
  6671. if (!document.getElementById('kxs-online-style')) {
  6672. const style = document.createElement('style');
  6673. style.id = 'kxs-online-style';
  6674. style.innerHTML = `
  6675. @keyframes kxs-pulse {
  6676. 0% { box-shadow:0 0 8px #3fae2a; opacity: 1; }
  6677. 100% { box-shadow:0 0 16px #3fae2a; opacity: 0.6; }
  6678. }
  6679. `;
  6680. document.head.appendChild(style);
  6681. }
  6682. overlay.appendChild(menu);
  6683. this.onlineMenuElement = menu;
  6684. this.updateOnlineMenu();
  6685. this.onlineMenuInterval = window.setInterval(() => this.updateOnlineMenu(), 2000);
  6686. }
  6687. updateOnlineMenu() {
  6688. return KxsClient_awaiter(this, void 0, void 0, function* () {
  6689. if (!this.onlineMenuElement)
  6690. return;
  6691. const countEl = this.onlineMenuElement.querySelector('#kxs-online-count');
  6692. const dot = this.onlineMenuElement.querySelector('#kxs-online-dot');
  6693. try {
  6694. const res = this.kxsNetwork.getOnlineCount();
  6695. const count = typeof res === 'number' ? res : '?';
  6696. if (countEl)
  6697. countEl.textContent = `${count} Kxs users`;
  6698. if (dot) {
  6699. dot.style.background = '#3fae2a';
  6700. dot.style.boxShadow = '0 0 8px #3fae2a';
  6701. dot.style.animation = 'kxs-pulse 1s infinite alternate';
  6702. }
  6703. }
  6704. catch (e) {
  6705. if (countEl)
  6706. countEl.textContent = 'API offline';
  6707. if (dot) {
  6708. dot.style.background = '#888';
  6709. dot.style.boxShadow = 'none';
  6710. dot.style.animation = '';
  6711. }
  6712. }
  6713. });
  6714. }
  6715. detectDeviceType() {
  6716. const ua = navigator.userAgent;
  6717. if (/Mobi|Android/i.test(ua)) {
  6718. if (/Tablet|iPad/i.test(ua)) {
  6719. return "tablet";
  6720. }
  6721. return "mobile";
  6722. }
  6723. if (/iPad|Tablet/i.test(ua) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1)) {
  6724. return "tablet";
  6725. }
  6726. return "desktop";
  6727. }
  6728. isMobile() {
  6729. return this.detectDeviceType() !== "desktop";
  6730. }
  6731. updateLocalStorage() {
  6732. localStorage.setItem("userSettings", JSON.stringify({
  6733. isFpsVisible: this.isFpsVisible,
  6734. isPingVisible: this.isPingVisible,
  6735. isFpsUncapped: this.isFpsUncapped,
  6736. isKillsVisible: this.isKillsVisible,
  6737. discordWebhookUrl: this.discordWebhookUrl,
  6738. isDeathSoundEnabled: this.isDeathSoundEnabled,
  6739. isWinSoundEnabled: this.isWinSoundEnabled,
  6740. isHealthWarningEnabled: this.isHealthWarningEnabled,
  6741. isAutoUpdateEnabled: this.isAutoUpdateEnabled,
  6742. isWinningAnimationEnabled: this.isWinningAnimationEnabled,
  6743. discordToken: this.discordToken,
  6744. isKillLeaderTrackerEnabled: this.isKillLeaderTrackerEnabled,
  6745. isKillFeedBlint: this.isKillFeedBlint,
  6746. all_friends: this.all_friends,
  6747. isSpotifyPlayerEnabled: this.isSpotifyPlayerEnabled,
  6748. isMainMenuCleaned: this.isMainMenuCleaned,
  6749. isNotifyingForToggleMenu: this.isNotifyingForToggleMenu,
  6750. soundLibrary: this.soundLibrary,
  6751. customCrosshair: this.customCrosshair,
  6752. isGunOverlayColored: this.isGunOverlayColored,
  6753. isGunBorderChromatic: this.isGunBorderChromatic,
  6754. isVoiceChatEnabled: this.isVoiceChatEnabled,
  6755. isKxsChatEnabled: this.isKxsChatEnabled,
  6756. kxsNetworkSettings: this.kxsNetworkSettings,
  6757. isHealBarIndicatorEnabled: this.isHealBarIndicatorEnabled
  6758. }));
  6759. }
  6760. ;
  6761. initDeathDetection() {
  6762. const config = {
  6763. childList: true,
  6764. subtree: true,
  6765. attributes: false,
  6766. characterData: false,
  6767. };
  6768. this.deathObserver = new MutationObserver((mutations) => {
  6769. for (const mutation of mutations) {
  6770. if (mutation.addedNodes.length) {
  6771. this.checkForDeathScreen(mutation.addedNodes);
  6772. }
  6773. }
  6774. });
  6775. this.deathObserver.observe(document.body, config);
  6776. }
  6777. checkForDeathScreen(nodes) {
  6778. let loseArray = [
  6779. "died",
  6780. "eliminated",
  6781. "was"
  6782. ];
  6783. let winArray = [
  6784. "Winner",
  6785. "Victory",
  6786. "dinner",
  6787. ];
  6788. nodes.forEach((node) => {
  6789. var _a;
  6790. if (node instanceof HTMLElement) {
  6791. const deathTitle = node.querySelector(".ui-stats-header-title");
  6792. const deathTitle_2 = node.querySelector(".ui-stats-title");
  6793. if (loseArray.some((word) => { var _a; return (_a = deathTitle === null || deathTitle === void 0 ? void 0 : deathTitle.textContent) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(word); })) {
  6794. this.kxsNetwork.gameEnded();
  6795. this.handlePlayerDeath();
  6796. }
  6797. else if (winArray.some((word) => { var _a; return (_a = deathTitle === null || deathTitle === void 0 ? void 0 : deathTitle.textContent) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(word); })) {
  6798. this.kxsNetwork.gameEnded();
  6799. this.handlePlayerWin();
  6800. }
  6801. else if ((_a = deathTitle_2 === null || deathTitle_2 === void 0 ? void 0 : deathTitle_2.textContent) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes("result")) {
  6802. this.kxsNetwork.gameEnded();
  6803. this.handlePlayerDeath();
  6804. }
  6805. }
  6806. });
  6807. }
  6808. handlePlayerDeath() {
  6809. return KxsClient_awaiter(this, void 0, void 0, function* () {
  6810. try {
  6811. if (this.isDeathSoundEnabled) {
  6812. const audio = new Audio(this.soundLibrary.death_sound_url);
  6813. audio.volume = 0.3;
  6814. audio.play().catch((err) => false);
  6815. }
  6816. }
  6817. catch (error) {
  6818. this.logger.error("Reading error:", error);
  6819. }
  6820. const stats = this.getPlayerStats(false);
  6821. const body = {
  6822. username: stats.username,
  6823. kills: stats.kills,
  6824. damageDealt: stats.damageDealt,
  6825. damageTaken: stats.damageTaken,
  6826. duration: stats.duration,
  6827. position: stats.position,
  6828. isWin: false,
  6829. };
  6830. yield this.discordTracker.trackGameEnd(body);
  6831. this.db.set(new Date().toISOString(), body);
  6832. });
  6833. }
  6834. handlePlayerWin() {
  6835. return KxsClient_awaiter(this, void 0, void 0, function* () {
  6836. var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
  6837. if (this.isWinningAnimationEnabled) {
  6838. this.felicitation();
  6839. }
  6840. const stats = this.getPlayerStats(true);
  6841. const body = {
  6842. username: stats.username,
  6843. kills: stats.kills,
  6844. damageDealt: stats.damageDealt,
  6845. damageTaken: stats.damageTaken,
  6846. duration: stats.duration,
  6847. position: stats.position,
  6848. isWin: true,
  6849. stuff: {
  6850. main_weapon: (_a = document.querySelector('#ui-weapon-id-1 .ui-weapon-name')) === null || _a === void 0 ? void 0 : _a.textContent,
  6851. secondary_weapon: (_b = document.querySelector('#ui-weapon-id-2 .ui-weapon-name')) === null || _b === void 0 ? void 0 : _b.textContent,
  6852. soda: (_c = document.querySelector("#ui-loot-soda .ui-loot-count")) === null || _c === void 0 ? void 0 : _c.textContent,
  6853. melees: (_d = document.querySelector('#ui-weapon-id-3 .ui-weapon-name')) === null || _d === void 0 ? void 0 : _d.textContent,
  6854. grenades: (_e = document.querySelector(`#ui-weapon-id-4 .ui-weapon-name`)) === null || _e === void 0 ? void 0 : _e.textContent,
  6855. medkit: (_f = document.querySelector("#ui-loot-healthkit .ui-loot-count")) === null || _f === void 0 ? void 0 : _f.textContent,
  6856. bandage: (_g = document.querySelector("#ui-loot-bandage .ui-loot-count")) === null || _g === void 0 ? void 0 : _g.textContent,
  6857. pills: (_h = document.querySelector("#ui-loot-painkiller .ui-loot-count")) === null || _h === void 0 ? void 0 : _h.textContent,
  6858. backpack: (_j = document.querySelector("#ui-armor-backpack .ui-armor-level")) === null || _j === void 0 ? void 0 : _j.textContent,
  6859. chest: (_k = document.querySelector("#ui-armor-chest .ui-armor-level")) === null || _k === void 0 ? void 0 : _k.textContent,
  6860. helmet: (_l = document.querySelector("#ui-armor-helmet .ui-armor-level")) === null || _l === void 0 ? void 0 : _l.textContent,
  6861. }
  6862. };
  6863. yield this.discordTracker.trackGameEnd(body);
  6864. this.db.set(new Date().toISOString(), body);
  6865. });
  6866. }
  6867. felicitation() {
  6868. const goldText = document.createElement("div");
  6869. goldText.textContent = "#1";
  6870. goldText.style.position = "fixed";
  6871. goldText.style.top = "50%";
  6872. goldText.style.left = "50%";
  6873. goldText.style.transform = "translate(-50%, -50%)";
  6874. goldText.style.fontSize = "80px";
  6875. goldText.style.color = "gold";
  6876. goldText.style.textShadow = "2px 2px 4px rgba(0,0,0,0.3)";
  6877. goldText.style.zIndex = "10000";
  6878. document.body.appendChild(goldText);
  6879. function createConfetti() {
  6880. const colors = [
  6881. "#ff0000",
  6882. "#00ff00",
  6883. "#0000ff",
  6884. "#ffff00",
  6885. "#ff00ff",
  6886. "#00ffff",
  6887. "gold",
  6888. ];
  6889. const confetti = document.createElement("div");
  6890. confetti.style.position = "fixed";
  6891. confetti.style.width = Math.random() * 10 + 5 + "px";
  6892. confetti.style.height = Math.random() * 10 + 5 + "px";
  6893. confetti.style.backgroundColor =
  6894. colors[Math.floor(Math.random() * colors.length)];
  6895. confetti.style.borderRadius = "50%";
  6896. confetti.style.zIndex = "9999";
  6897. confetti.style.left = Math.random() * 100 + "vw";
  6898. confetti.style.top = "-20px";
  6899. document.body.appendChild(confetti);
  6900. let posY = -20;
  6901. let posX = parseFloat(confetti.style.left);
  6902. let rotation = 0;
  6903. let speedY = Math.random() * 2 + 1;
  6904. let speedX = Math.random() * 2 - 1;
  6905. function fall() {
  6906. posY += speedY;
  6907. posX += speedX;
  6908. rotation += 5;
  6909. confetti.style.top = posY + "px";
  6910. confetti.style.left = posX + "vw";
  6911. confetti.style.transform = `rotate(${rotation}deg)`;
  6912. if (posY < window.innerHeight) {
  6913. requestAnimationFrame(fall);
  6914. }
  6915. else {
  6916. confetti.remove();
  6917. }
  6918. }
  6919. fall();
  6920. }
  6921. const confettiInterval = setInterval(() => {
  6922. for (let i = 0; i < 5; i++) {
  6923. createConfetti();
  6924. }
  6925. }, 100);
  6926. if (this.isWinSoundEnabled) {
  6927. const audio = new Audio(this.soundLibrary.win_sound_url);
  6928. audio.play().catch((err) => this.logger.error("Erreur lecture:", err));
  6929. }
  6930. setTimeout(() => {
  6931. clearInterval(confettiInterval);
  6932. goldText.style.transition = "opacity 1s";
  6933. goldText.style.opacity = "0";
  6934. setTimeout(() => goldText.remove(), 1000);
  6935. }, 5000);
  6936. }
  6937. cleanup() {
  6938. if (this.deathObserver) {
  6939. this.deathObserver.disconnect();
  6940. this.deathObserver = null;
  6941. }
  6942. }
  6943. getUsername() {
  6944. const configKey = "surviv_config";
  6945. const savedConfig = localStorage.getItem(configKey);
  6946. const config = JSON.parse(savedConfig);
  6947. if (config.playerName) {
  6948. return config.playerName;
  6949. }
  6950. else {
  6951. return "Player";
  6952. }
  6953. }
  6954. getPlayerStats(win) {
  6955. const statsInfo = win
  6956. ? document.querySelector(".ui-stats-info-player")
  6957. : document.querySelector(".ui-stats-info-player.ui-stats-info-status");
  6958. const rank = document.querySelector(".ui-stats-header-value");
  6959. if (!(statsInfo === null || statsInfo === void 0 ? void 0 : statsInfo.textContent) || !(rank === null || rank === void 0 ? void 0 : rank.textContent)) {
  6960. return {
  6961. username: this.getUsername(),
  6962. kills: 0,
  6963. damageDealt: 0,
  6964. damageTaken: 0,
  6965. duration: "0s",
  6966. position: "#unknown",
  6967. };
  6968. }
  6969. const parsedStats = StatsParser.parse(statsInfo.textContent, rank === null || rank === void 0 ? void 0 : rank.textContent);
  6970. parsedStats.username = this.getUsername();
  6971. return parsedStats;
  6972. }
  6973. setAnimationFrameCallback() {
  6974. this.animationFrameCallback = this.isFpsUncapped
  6975. ? (callback) => setTimeout(callback, 1)
  6976. : window.requestAnimationFrame.bind(window);
  6977. }
  6978. makeResizable(element, storageKey) {
  6979. let isResizing = false;
  6980. let startX, startY, startWidth, startHeight;
  6981. // Add a resize area in the bottom right
  6982. const resizer = document.createElement("div");
  6983. Object.assign(resizer.style, {
  6984. width: "10px",
  6985. height: "10px",
  6986. backgroundColor: "white",
  6987. position: "absolute",
  6988. right: "0",
  6989. bottom: "0",
  6990. cursor: "nwse-resize",
  6991. zIndex: "10001",
  6992. });
  6993. element.appendChild(resizer);
  6994. resizer.addEventListener("mousedown", (event) => {
  6995. isResizing = true;
  6996. startX = event.clientX;
  6997. startY = event.clientY;
  6998. startWidth = element.offsetWidth;
  6999. startHeight = element.offsetHeight;
  7000. event.stopPropagation(); // Empêche l'activation du déplacement
  7001. });
  7002. window.addEventListener("mousemove", (event) => {
  7003. if (isResizing) {
  7004. const newWidth = startWidth + (event.clientX - startX);
  7005. const newHeight = startHeight + (event.clientY - startY);
  7006. element.style.width = `${newWidth}px`;
  7007. element.style.height = `${newHeight}px`;
  7008. // Sauvegarde de la taille
  7009. localStorage.setItem(storageKey, JSON.stringify({
  7010. width: newWidth,
  7011. height: newHeight,
  7012. }));
  7013. }
  7014. });
  7015. window.addEventListener("mouseup", () => {
  7016. isResizing = false;
  7017. });
  7018. const savedSize = localStorage.getItem(storageKey);
  7019. if (savedSize) {
  7020. const { width, height } = JSON.parse(savedSize);
  7021. element.style.width = `${width}px`;
  7022. element.style.height = `${height}px`;
  7023. }
  7024. else {
  7025. element.style.width = "150px"; // Taille par défaut
  7026. element.style.height = "50px";
  7027. }
  7028. }
  7029. makeDraggable(element, storageKey) {
  7030. let isDragging = false;
  7031. let dragOffset = { x: 0, y: 0 };
  7032. element.addEventListener("mousedown", (event) => {
  7033. if (event.button === 0) {
  7034. // Left click only
  7035. isDragging = true;
  7036. this.gridSystem.toggleGrid(); // Afficher la grille quand on commence à déplacer
  7037. dragOffset = {
  7038. x: event.clientX - element.offsetLeft,
  7039. y: event.clientY - element.offsetTop,
  7040. };
  7041. element.style.cursor = "grabbing";
  7042. }
  7043. });
  7044. window.addEventListener("mousemove", (event) => {
  7045. if (isDragging) {
  7046. const rawX = event.clientX - dragOffset.x;
  7047. const rawY = event.clientY - dragOffset.y;
  7048. // Get snapped coordinates from grid system
  7049. const snapped = this.gridSystem.snapToGrid(element, rawX, rawY);
  7050. // Prevent moving off screen
  7051. const maxX = window.innerWidth - element.offsetWidth;
  7052. const maxY = window.innerHeight - element.offsetHeight;
  7053. element.style.left = `${Math.max(0, Math.min(snapped.x, maxX))}px`;
  7054. element.style.top = `${Math.max(0, Math.min(snapped.y, maxY))}px`;
  7055. // Highlight nearest grid lines while dragging
  7056. this.gridSystem.highlightNearestGridLine(rawX, rawY);
  7057. // Save position
  7058. localStorage.setItem(storageKey, JSON.stringify({
  7059. x: parseInt(element.style.left),
  7060. y: parseInt(element.style.top),
  7061. }));
  7062. }
  7063. });
  7064. window.addEventListener("mouseup", () => {
  7065. if (isDragging) {
  7066. isDragging = false;
  7067. this.gridSystem.toggleGrid(); // Masquer la grille quand on arrête de déplacer
  7068. element.style.cursor = "move";
  7069. }
  7070. });
  7071. // Load saved position
  7072. const savedPosition = localStorage.getItem(storageKey);
  7073. if (savedPosition) {
  7074. const { x, y } = JSON.parse(savedPosition);
  7075. const snapped = this.gridSystem.snapToGrid(element, x, y);
  7076. element.style.left = `${snapped.x}px`;
  7077. element.style.top = `${snapped.y}px`;
  7078. }
  7079. }
  7080. getKills() {
  7081. const killElement = document.querySelector(".ui-player-kills.js-ui-player-kills");
  7082. if (killElement) {
  7083. const kills = parseInt(killElement.textContent || "", 10);
  7084. return isNaN(kills) ? 0 : kills;
  7085. }
  7086. return 0;
  7087. }
  7088. getRegionFromLocalStorage() {
  7089. let config = localStorage.getItem("surviv_config");
  7090. if (config) {
  7091. let configObject = JSON.parse(config);
  7092. return configObject.region;
  7093. }
  7094. return null;
  7095. }
  7096. saveBackgroundToLocalStorage(image) {
  7097. if (typeof image === "string") {
  7098. localStorage.setItem("lastBackgroundUrl", image);
  7099. }
  7100. if (typeof image === "string") {
  7101. localStorage.setItem("lastBackgroundType", "url");
  7102. localStorage.setItem("lastBackgroundValue", image);
  7103. }
  7104. else {
  7105. localStorage.setItem("lastBackgroundType", "local");
  7106. const reader = new FileReader();
  7107. reader.onload = () => {
  7108. localStorage.setItem("lastBackgroundValue", reader.result);
  7109. };
  7110. reader.readAsDataURL(image);
  7111. }
  7112. }
  7113. loadBackgroundFromLocalStorage() {
  7114. const backgroundType = localStorage.getItem("lastBackgroundType");
  7115. const backgroundValue = localStorage.getItem("lastBackgroundValue");
  7116. const backgroundElement = document.getElementById("background");
  7117. if (backgroundElement && backgroundType && backgroundValue) {
  7118. if (backgroundType === "url") {
  7119. backgroundElement.style.backgroundImage = `url(${backgroundValue})`;
  7120. }
  7121. else if (backgroundType === "local") {
  7122. backgroundElement.style.backgroundImage = `url(${backgroundValue})`;
  7123. }
  7124. }
  7125. }
  7126. loadLocalStorage() {
  7127. var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
  7128. const savedSettings = localStorage.getItem("userSettings")
  7129. ? JSON.parse(localStorage.getItem("userSettings"))
  7130. : null;
  7131. if (savedSettings) {
  7132. this.isFpsVisible = (_a = savedSettings.isFpsVisible) !== null && _a !== void 0 ? _a : this.isFpsVisible;
  7133. this.isPingVisible = (_b = savedSettings.isPingVisible) !== null && _b !== void 0 ? _b : this.isPingVisible;
  7134. this.isFpsUncapped = (_c = savedSettings.isFpsUncapped) !== null && _c !== void 0 ? _c : this.isFpsUncapped;
  7135. this.isKillsVisible = (_d = savedSettings.isKillsVisible) !== null && _d !== void 0 ? _d : this.isKillsVisible;
  7136. this.discordWebhookUrl = (_e = savedSettings.discordWebhookUrl) !== null && _e !== void 0 ? _e : this.discordWebhookUrl;
  7137. this.isHealthWarningEnabled = (_f = savedSettings.isHealthWarningEnabled) !== null && _f !== void 0 ? _f : this.isHealthWarningEnabled;
  7138. this.isAutoUpdateEnabled = (_g = savedSettings.isAutoUpdateEnabled) !== null && _g !== void 0 ? _g : this.isAutoUpdateEnabled;
  7139. this.isWinningAnimationEnabled = (_h = savedSettings.isWinningAnimationEnabled) !== null && _h !== void 0 ? _h : this.isWinningAnimationEnabled;
  7140. this.discordToken = (_j = savedSettings.discordToken) !== null && _j !== void 0 ? _j : this.discordToken;
  7141. this.isKillLeaderTrackerEnabled = (_k = savedSettings.isKillLeaderTrackerEnabled) !== null && _k !== void 0 ? _k : this.isKillLeaderTrackerEnabled;
  7142. this.isKillFeedBlint = (_l = savedSettings.isKillFeedBlint) !== null && _l !== void 0 ? _l : this.isKillFeedBlint;
  7143. this.all_friends = (_m = savedSettings.all_friends) !== null && _m !== void 0 ? _m : this.all_friends;
  7144. this.isSpotifyPlayerEnabled = (_o = savedSettings.isSpotifyPlayerEnabled) !== null && _o !== void 0 ? _o : this.isSpotifyPlayerEnabled;
  7145. this.isMainMenuCleaned = (_p = savedSettings.isMainMenuCleaned) !== null && _p !== void 0 ? _p : this.isMainMenuCleaned;
  7146. this.isNotifyingForToggleMenu = (_q = savedSettings.isNotifyingForToggleMenu) !== null && _q !== void 0 ? _q : this.isNotifyingForToggleMenu;
  7147. this.customCrosshair = (_r = savedSettings.customCrosshair) !== null && _r !== void 0 ? _r : this.customCrosshair;
  7148. this.isGunOverlayColored = (_s = savedSettings.isGunOverlayColored) !== null && _s !== void 0 ? _s : this.isGunOverlayColored;
  7149. this.isGunBorderChromatic = (_t = savedSettings.isGunBorderChromatic) !== null && _t !== void 0 ? _t : this.isGunBorderChromatic;
  7150. this.isVoiceChatEnabled = (_u = savedSettings.isVoiceChatEnabled) !== null && _u !== void 0 ? _u : this.isVoiceChatEnabled;
  7151. this.isKxsChatEnabled = (_v = savedSettings.isKxsChatEnabled) !== null && _v !== void 0 ? _v : this.isKxsChatEnabled;
  7152. this.kxsNetworkSettings = (_w = savedSettings.kxsNetworkSettings) !== null && _w !== void 0 ? _w : this.kxsNetworkSettings;
  7153. this.isHealBarIndicatorEnabled = (_x = savedSettings.isHealBarIndicatorEnabled) !== null && _x !== void 0 ? _x : this.isHealBarIndicatorEnabled;
  7154. this.isWinSoundEnabled = (_y = savedSettings.isWinSoundEnabled) !== null && _y !== void 0 ? _y : this.isWinSoundEnabled;
  7155. this.isDeathSoundEnabled = (_z = savedSettings.isDeathSoundEnabled) !== null && _z !== void 0 ? _z : this.isDeathSoundEnabled;
  7156. if (savedSettings.soundLibrary) {
  7157. // Check if the sound value exists
  7158. if (savedSettings.soundLibrary.win_sound_url) {
  7159. this.soundLibrary.win_sound_url = savedSettings.soundLibrary.win_sound_url;
  7160. }
  7161. if (savedSettings.soundLibrary.death_sound_url) {
  7162. this.soundLibrary.death_sound_url = savedSettings.soundLibrary.death_sound_url;
  7163. }
  7164. if (savedSettings.soundLibrary.background_sound_url) {
  7165. this.soundLibrary.background_sound_url = savedSettings.soundLibrary.background_sound_url;
  7166. }
  7167. }
  7168. }
  7169. this.updateKillsVisibility();
  7170. this.updateFpsVisibility();
  7171. this.updatePingVisibility();
  7172. }
  7173. updateFpsVisibility() {
  7174. if (this.counters.fps) {
  7175. this.counters.fps.style.display = this.isFpsVisible ? "block" : "none";
  7176. this.counters.fps.style.backgroundColor = this.isFpsVisible
  7177. ? "rgba(0, 0, 0, 0.2)"
  7178. : "transparent";
  7179. }
  7180. }
  7181. updatePingVisibility() {
  7182. if (this.counters.ping) {
  7183. this.counters.ping.style.display = this.isPingVisible ? "block" : "none";
  7184. }
  7185. }
  7186. updateKillsVisibility() {
  7187. if (this.counters.kills) {
  7188. this.counters.kills.style.display = this.isKillsVisible
  7189. ? "block"
  7190. : "none";
  7191. this.counters.kills.style.backgroundColor = this.isKillsVisible
  7192. ? "rgba(0, 0, 0, 0.2)"
  7193. : "transparent";
  7194. }
  7195. }
  7196. createSimpleSpotifyPlayer() {
  7197. // Ajouter une règle CSS globale pour supprimer toutes les bordures et améliorer le redimensionnement
  7198. const styleElement = document.createElement('style');
  7199. styleElement.textContent = `
  7200. #spotify-player-container,
  7201. #spotify-player-container *,
  7202. #spotify-player-iframe,
  7203. .spotify-resize-handle {
  7204. border: none !important;
  7205. outline: none !important;
  7206. box-sizing: content-box !important;
  7207. }
  7208. #spotify-player-iframe {
  7209. padding-bottom: 0 !important;
  7210. margin-bottom: 0 !important;
  7211. }
  7212. .spotify-resize-handle {
  7213. touch-action: none;
  7214. backface-visibility: hidden;
  7215. }
  7216. .spotify-resizing {
  7217. user-select: none !important;
  7218. pointer-events: none !important;
  7219. }
  7220. .spotify-resizing .spotify-resize-handle {
  7221. pointer-events: all !important;
  7222. }
  7223. `;
  7224. document.head.appendChild(styleElement);
  7225. // Main container
  7226. const container = document.createElement('div');
  7227. container.id = 'spotify-player-container';
  7228. // Récupérer la position sauvegardée si disponible
  7229. const savedLeft = localStorage.getItem('kxsSpotifyPlayerLeft');
  7230. const savedTop = localStorage.getItem('kxsSpotifyPlayerTop');
  7231. Object.assign(container.style, {
  7232. position: 'fixed',
  7233. width: '320px',
  7234. backgroundColor: '#121212',
  7235. borderRadius: '0px',
  7236. boxShadow: 'none',
  7237. overflow: 'hidden',
  7238. zIndex: '10000',
  7239. fontFamily: 'Montserrat, Arial, sans-serif',
  7240. transition: 'transform 0.3s ease, opacity 0.3s ease',
  7241. transform: 'translateY(0)',
  7242. opacity: '1'
  7243. });
  7244. // Appliquer la position sauvegardée ou la position par défaut
  7245. if (savedLeft && savedTop) {
  7246. container.style.left = savedLeft;
  7247. container.style.top = savedTop;
  7248. container.style.right = 'auto';
  7249. container.style.bottom = 'auto';
  7250. }
  7251. else {
  7252. container.style.right = '20px';
  7253. container.style.bottom = '20px';
  7254. }
  7255. // Player header
  7256. const header = document.createElement('div');
  7257. Object.assign(header.style, {
  7258. display: 'flex',
  7259. alignItems: 'center',
  7260. justifyContent: 'space-between',
  7261. padding: '12px 16px',
  7262. backgroundColor: '#070707',
  7263. color: 'white',
  7264. borderBottom: 'none',
  7265. position: 'relative' // For absolute positioning of the button
  7266. });
  7267. // Spotify logo
  7268. const logo = document.createElement('div');
  7269. logo.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><path fill="#1DB954" d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z"/></svg>`;
  7270. const title = document.createElement('span');
  7271. title.textContent = 'Spotify Player';
  7272. title.style.marginLeft = '8px';
  7273. title.style.fontWeight = 'bold';
  7274. const logoContainer = document.createElement('div');
  7275. logoContainer.style.display = 'flex';
  7276. logoContainer.style.alignItems = 'center';
  7277. logoContainer.appendChild(logo);
  7278. logoContainer.appendChild(title);
  7279. // Control buttons
  7280. const controls = document.createElement('div');
  7281. controls.style.display = 'flex';
  7282. controls.style.alignItems = 'center';
  7283. // Minimize button
  7284. const minimizeBtn = document.createElement('button');
  7285. Object.assign(minimizeBtn.style, {
  7286. background: 'none',
  7287. border: 'none',
  7288. color: '#aaa',
  7289. cursor: 'pointer',
  7290. fontSize: '18px',
  7291. padding: '0',
  7292. marginLeft: '10px',
  7293. width: '24px',
  7294. height: '24px',
  7295. display: 'flex',
  7296. alignItems: 'center',
  7297. justifyContent: 'center'
  7298. });
  7299. minimizeBtn.innerHTML = '−';
  7300. minimizeBtn.title = 'Minimize';
  7301. // Close button
  7302. const closeBtn = document.createElement('button');
  7303. Object.assign(closeBtn.style, {
  7304. background: 'none',
  7305. border: 'none',
  7306. color: '#aaa',
  7307. cursor: 'pointer',
  7308. fontSize: '18px',
  7309. padding: '0',
  7310. marginLeft: '10px',
  7311. width: '24px',
  7312. height: '24px',
  7313. display: 'flex',
  7314. alignItems: 'center',
  7315. justifyContent: 'center'
  7316. });
  7317. closeBtn.innerHTML = '×';
  7318. closeBtn.title = 'Close';
  7319. controls.appendChild(minimizeBtn);
  7320. controls.appendChild(closeBtn);
  7321. header.appendChild(logoContainer);
  7322. header.appendChild(controls);
  7323. // Album cover image
  7324. const albumArt = document.createElement('div');
  7325. Object.assign(albumArt.style, {
  7326. width: '50px',
  7327. height: '50px',
  7328. backgroundColor: '#333',
  7329. backgroundSize: 'cover',
  7330. backgroundPosition: 'center',
  7331. borderRadius: '4px',
  7332. flexShrink: '0'
  7333. });
  7334. albumArt.style.backgroundImage = `url('https://i.scdn.co/image/ab67616d00001e02fe24b9ffeb3c3fdb4f9abbe9')`;
  7335. // Track information
  7336. const trackInfo = document.createElement('div');
  7337. Object.assign(trackInfo.style, {
  7338. flex: '1',
  7339. overflow: 'hidden'
  7340. });
  7341. // Player content
  7342. const content = document.createElement('div');
  7343. content.style.padding = '0';
  7344. // Spotify iframe
  7345. const iframe = document.createElement('iframe');
  7346. iframe.id = 'spotify-player-iframe';
  7347. iframe.src = 'https://open.spotify.com/embed/playlist/37i9dQZEVXcJZyENOWUFo7?utm_source=generator&theme=1';
  7348. iframe.width = '100%';
  7349. iframe.height = '152px';
  7350. iframe.frameBorder = '0';
  7351. iframe.allow = 'autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture';
  7352. iframe.style.border = 'none';
  7353. iframe.style.margin = '0';
  7354. iframe.style.padding = '0';
  7355. iframe.style.boxSizing = 'content-box';
  7356. iframe.style.display = 'block'; // Forcer display block pour éviter les problèmes d'espacement
  7357. iframe.setAttribute('frameBorder', '0');
  7358. iframe.setAttribute('allowtransparency', 'true');
  7359. iframe.setAttribute('scrolling', 'no'); // Désactiver le défilement interne
  7360. content.appendChild(iframe);
  7361. // Playlist change button integrated in the header
  7362. const changePlaylistContainer = document.createElement('div');
  7363. Object.assign(changePlaylistContainer.style, {
  7364. display: 'flex',
  7365. alignItems: 'center',
  7366. marginRight: '10px'
  7367. });
  7368. // Square button to enter a playlist ID
  7369. const changePlaylistBtn = document.createElement('button');
  7370. Object.assign(changePlaylistBtn.style, {
  7371. width: '24px',
  7372. height: '24px',
  7373. backgroundColor: '#1DB954',
  7374. color: 'white',
  7375. border: 'none',
  7376. borderRadius: '4px',
  7377. fontSize: '14px',
  7378. fontWeight: 'bold',
  7379. cursor: 'pointer',
  7380. display: 'flex',
  7381. alignItems: 'center',
  7382. justifyContent: 'center',
  7383. margin: '0 8px 0 0'
  7384. });
  7385. changePlaylistBtn.innerHTML = `
  7386. <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
  7387. <path d="M12 5V19M5 12H19" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
  7388. </svg>
  7389. `;
  7390. changePlaylistBtn.addEventListener('click', () => {
  7391. const id = prompt('Enter the Spotify playlist ID:', '37i9dQZEVXcJZyENOWUFo7');
  7392. if (id) {
  7393. iframe.src = `https://open.spotify.com/embed/playlist/${id}?utm_source=generator&theme=0`;
  7394. localStorage.setItem('kxsSpotifyPlaylist', id);
  7395. // Simulate an album cover based on the playlist ID
  7396. albumArt.style.backgroundImage = `url('https://i.scdn.co/image/ab67706f00000002${id.substring(0, 16)}')`;
  7397. }
  7398. });
  7399. changePlaylistContainer.appendChild(changePlaylistBtn);
  7400. // Load saved playlist
  7401. const savedPlaylist = localStorage.getItem('kxsSpotifyPlaylist');
  7402. if (savedPlaylist) {
  7403. iframe.src = `https://open.spotify.com/embed/playlist/${savedPlaylist}?utm_source=generator&theme=0`;
  7404. // Simulate an album cover based on the playlist ID
  7405. albumArt.style.backgroundImage = `url('https://i.scdn.co/image/ab67706f00000002${savedPlaylist.substring(0, 16)}')`;
  7406. }
  7407. // Integrate the playlist change button into the controls
  7408. controls.insertBefore(changePlaylistContainer, minimizeBtn);
  7409. // Assemble the elements
  7410. container.appendChild(header);
  7411. container.appendChild(content);
  7412. // Add a title to the button for accessibility
  7413. changePlaylistBtn.title = "Change playlist";
  7414. // Add to document
  7415. document.body.appendChild(container);
  7416. // Ajouter un bord redimensionnable au lecteur
  7417. const resizeHandle = document.createElement('div');
  7418. resizeHandle.className = 'spotify-resize-handle';
  7419. Object.assign(resizeHandle.style, {
  7420. position: 'absolute',
  7421. bottom: '0',
  7422. right: '0',
  7423. width: '30px',
  7424. height: '30px',
  7425. cursor: 'nwse-resize',
  7426. background: 'rgba(255, 255, 255, 0.1)',
  7427. zIndex: '10001',
  7428. pointerEvents: 'all'
  7429. });
  7430. // Ajouter un indicateur visuel de redimensionnement plus visible
  7431. resizeHandle.innerHTML = `
  7432. <svg width="14" height="14" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg" style="position: absolute; bottom: 4px; right: 4px;">
  7433. <path d="M9 9L5 9L9 5L9 9Z" fill="white"/>
  7434. <path d="M5 9L1 9L9 1L9 5L5 9Z" fill="white"/>
  7435. <path d="M1 9L1 5L5 1L9 1L1 9Z" fill="white"/>
  7436. <path d="M1 5L1 1L5 1L1 5Z" fill="white"/>
  7437. </svg>
  7438. `;
  7439. // Logique de redimensionnement
  7440. let isResizing = false;
  7441. let startX = 0, startY = 0;
  7442. let startWidth = 0, startHeight = 0;
  7443. resizeHandle.addEventListener('mousedown', (e) => {
  7444. // Arrêter la propagation pour éviter que d'autres éléments interceptent l'événement
  7445. e.stopPropagation();
  7446. e.preventDefault();
  7447. isResizing = true;
  7448. startX = e.clientX;
  7449. startY = e.clientY;
  7450. startWidth = container.offsetWidth;
  7451. startHeight = container.offsetHeight;
  7452. // Ajouter une classe spéciale pendant le redimensionnement
  7453. container.classList.add('spotify-resizing');
  7454. // Appliquer le style pendant le redimensionnement
  7455. container.style.transition = 'none';
  7456. container.style.border = 'none';
  7457. container.style.outline = 'none';
  7458. iframe.style.border = 'none';
  7459. iframe.style.outline = 'none';
  7460. document.body.style.userSelect = 'none';
  7461. // Ajouter un overlay de redimensionnement temporairement
  7462. const resizeOverlay = document.createElement('div');
  7463. resizeOverlay.id = 'spotify-resize-overlay';
  7464. resizeOverlay.style.position = 'fixed';
  7465. resizeOverlay.style.top = '0';
  7466. resizeOverlay.style.left = '0';
  7467. resizeOverlay.style.width = '100%';
  7468. resizeOverlay.style.height = '100%';
  7469. resizeOverlay.style.zIndex = '9999';
  7470. resizeOverlay.style.cursor = 'nwse-resize';
  7471. resizeOverlay.style.background = 'transparent';
  7472. document.body.appendChild(resizeOverlay);
  7473. });
  7474. document.addEventListener('mousemove', (e) => {
  7475. if (!isResizing)
  7476. return;
  7477. // Calculer les nouvelles dimensions
  7478. const newWidth = startWidth + (e.clientX - startX);
  7479. const newHeight = startHeight + (e.clientY - startY);
  7480. // Limiter les dimensions minimales
  7481. const minWidth = 320; // Largeur minimale
  7482. const minHeight = 200; // Hauteur minimale
  7483. // Appliquer les nouvelles dimensions si elles sont supérieures aux minimums
  7484. if (newWidth >= minWidth) {
  7485. container.style.width = newWidth + 'px';
  7486. iframe.style.width = '100%';
  7487. }
  7488. if (newHeight >= minHeight) {
  7489. container.style.height = newHeight + 'px';
  7490. iframe.style.height = (newHeight - 50) + 'px'; // Ajuster la hauteur de l'iframe en conséquence
  7491. }
  7492. // Empêcher la sélection pendant le drag
  7493. e.preventDefault();
  7494. });
  7495. document.addEventListener('mouseup', () => {
  7496. if (isResizing) {
  7497. isResizing = false;
  7498. container.style.transition = 'transform 0.3s ease, opacity 0.3s ease';
  7499. container.style.border = 'none';
  7500. container.style.outline = 'none';
  7501. iframe.style.border = 'none';
  7502. iframe.style.outline = 'none';
  7503. document.body.style.userSelect = '';
  7504. // Supprimer l'overlay de redimensionnement
  7505. const overlay = document.getElementById('spotify-resize-overlay');
  7506. if (overlay)
  7507. overlay.remove();
  7508. // Supprimer la classe de redimensionnement
  7509. container.classList.remove('spotify-resizing');
  7510. // Sauvegarder les dimensions pour la prochaine fois
  7511. localStorage.setItem('kxsSpotifyPlayerWidth', container.style.width);
  7512. localStorage.setItem('kxsSpotifyPlayerHeight', container.style.height);
  7513. }
  7514. });
  7515. // Ajouter la poignée de redimensionnement au conteneur
  7516. container.appendChild(resizeHandle);
  7517. // Player states
  7518. let isMinimized = false;
  7519. // Events
  7520. minimizeBtn.addEventListener('click', () => {
  7521. if (isMinimized) {
  7522. content.style.display = 'block';
  7523. changePlaylistContainer.style.display = 'block';
  7524. container.style.transform = 'translateY(0)';
  7525. minimizeBtn.innerHTML = '−';
  7526. }
  7527. else {
  7528. content.style.display = 'none';
  7529. changePlaylistContainer.style.display = 'none';
  7530. container.style.transform = 'translateY(0)';
  7531. minimizeBtn.innerHTML = '+';
  7532. }
  7533. isMinimized = !isMinimized;
  7534. });
  7535. closeBtn.addEventListener('click', () => {
  7536. container.style.transform = 'translateY(150%)';
  7537. container.style.opacity = '0';
  7538. setTimeout(() => {
  7539. container.style.display = 'none';
  7540. showButton.style.display = 'flex';
  7541. showButton.style.alignItems = 'center';
  7542. showButton.style.justifyContent = 'center';
  7543. }, 300);
  7544. });
  7545. // Make the player draggable
  7546. let isDragging = false;
  7547. let offsetX = 0;
  7548. let offsetY = 0;
  7549. header.addEventListener('mousedown', (e) => {
  7550. isDragging = true;
  7551. offsetX = e.clientX - container.getBoundingClientRect().left;
  7552. offsetY = e.clientY - container.getBoundingClientRect().top;
  7553. container.style.transition = 'none';
  7554. });
  7555. document.addEventListener('mousemove', (e) => {
  7556. if (isDragging) {
  7557. container.style.right = 'auto';
  7558. container.style.bottom = 'auto';
  7559. container.style.left = (e.clientX - offsetX) + 'px';
  7560. container.style.top = (e.clientY - offsetY) + 'px';
  7561. }
  7562. });
  7563. document.addEventListener('mouseup', () => {
  7564. if (isDragging) {
  7565. isDragging = false;
  7566. container.style.transition = 'transform 0.3s ease, opacity 0.3s ease';
  7567. // Sauvegarder la position pour la prochaine fois
  7568. localStorage.setItem('kxsSpotifyPlayerLeft', container.style.left);
  7569. localStorage.setItem('kxsSpotifyPlayerTop', container.style.top);
  7570. }
  7571. });
  7572. // Button to show the player again
  7573. const showButton = document.createElement('button');
  7574. showButton.id = 'spotify-float-button';
  7575. Object.assign(showButton.style, {
  7576. position: 'fixed',
  7577. bottom: '20px',
  7578. right: '20px',
  7579. width: '50px',
  7580. height: '50px',
  7581. borderRadius: '50%',
  7582. backgroundColor: '#1DB954',
  7583. color: 'white',
  7584. border: 'none',
  7585. boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
  7586. cursor: 'pointer',
  7587. zIndex: '9999',
  7588. fontSize: '24px',
  7589. transition: 'transform 0.2s ease',
  7590. display: 'flex',
  7591. alignItems: 'center',
  7592. justifyContent: 'center'
  7593. });
  7594. showButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="white" d="M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12 12-5.4 12-12S18.66 0 12 0zm5.521 17.34c-.24.359-.66.48-1.021.24-2.82-1.74-6.36-2.101-10.561-1.141-.418.122-.779-.179-.899-.539-.12-.421.18-.78.54-.9 4.56-1.021 8.52-.6 11.64 1.32.42.18.479.659.301 1.02zm1.44-3.3c-.301.42-.841.6-1.262.3-3.239-1.98-8.159-2.58-11.939-1.38-.479.12-1.02-.12-1.14-.6-.12-.48.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2zm.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721-.18-.601.18-1.2.72-1.381 4.26-1.26 11.28-1.02 15.721 1.621.539.3.719 1.02.419 1.56-.299.421-1.02.599-1.559.3z"/></svg>`;
  7595. document.body.appendChild(showButton);
  7596. showButton.addEventListener('mouseenter', () => {
  7597. showButton.style.transform = 'scale(1.1)';
  7598. });
  7599. showButton.addEventListener('mouseleave', () => {
  7600. showButton.style.transform = 'scale(1)';
  7601. });
  7602. showButton.addEventListener('click', () => {
  7603. container.style.display = 'block';
  7604. container.style.transform = 'translateY(0)';
  7605. container.style.opacity = '1';
  7606. showButton.style.display = 'none';
  7607. });
  7608. return container;
  7609. }
  7610. toggleSpotifyMenu() {
  7611. if (this.isSpotifyPlayerEnabled) {
  7612. this.createSimpleSpotifyPlayer();
  7613. }
  7614. else {
  7615. this.removeSimpleSpotifyPlayer();
  7616. }
  7617. }
  7618. applyCustomMainMenuStyle() {
  7619. // Sélectionner le menu principal
  7620. const startMenu = document.getElementById('start-menu');
  7621. const playButtons = document.querySelectorAll('.btn-green, #btn-help, .btn-team-option');
  7622. const playerOptions = document.getElementById('player-options');
  7623. const serverSelect = document.getElementById('server-select-main');
  7624. const nameInput = document.getElementById('player-name-input-solo');
  7625. const helpSection = document.getElementById('start-help');
  7626. if (startMenu) {
  7627. // Apply styles to the main container
  7628. Object.assign(startMenu.style, {
  7629. background: 'linear-gradient(135deg, rgba(25, 25, 35, 0.95) 0%, rgba(15, 15, 25, 0.98) 100%)',
  7630. border: '1px solid rgba(255, 255, 255, 0.1)',
  7631. borderRadius: '12px',
  7632. boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
  7633. padding: '15px',
  7634. backdropFilter: 'blur(10px)',
  7635. margin: '0 auto'
  7636. });
  7637. }
  7638. // Style the buttons
  7639. playButtons.forEach(button => {
  7640. if (button instanceof HTMLElement) {
  7641. if (button.classList.contains('btn-green')) {
  7642. // Boutons Play
  7643. Object.assign(button.style, {
  7644. background: 'linear-gradient(135deg, #4287f5 0%, #3b76d9 100%)',
  7645. borderRadius: '8px',
  7646. border: '1px solid rgba(255, 255, 255, 0.2)',
  7647. boxShadow: '0 4px 12px rgba(0, 0, 0, 0.2)',
  7648. transition: 'all 0.2s ease',
  7649. color: 'white',
  7650. fontWeight: 'bold'
  7651. });
  7652. }
  7653. else {
  7654. // Autres boutons
  7655. Object.assign(button.style, {
  7656. background: 'rgba(40, 45, 60, 0.7)',
  7657. borderRadius: '8px',
  7658. border: '1px solid rgba(255, 255, 255, 0.1)',
  7659. transition: 'all 0.2s ease',
  7660. color: 'white'
  7661. });
  7662. }
  7663. // Hover effect for all buttons
  7664. button.addEventListener('mouseover', () => {
  7665. button.style.transform = 'translateY(-2px)';
  7666. button.style.boxShadow = '0 6px 16px rgba(0, 0, 0, 0.3)';
  7667. button.style.filter = 'brightness(1.1)';
  7668. });
  7669. button.addEventListener('mouseout', () => {
  7670. button.style.transform = 'translateY(0)';
  7671. button.style.boxShadow = button.classList.contains('btn-green') ?
  7672. '0 4px 12px rgba(0, 0, 0, 0.2)' : 'none';
  7673. button.style.filter = 'brightness(1)';
  7674. });
  7675. }
  7676. });
  7677. // Styliser le sélecteur de serveur
  7678. if (serverSelect instanceof HTMLSelectElement) {
  7679. Object.assign(serverSelect.style, {
  7680. background: 'rgba(30, 35, 50, 0.8)',
  7681. borderRadius: '8px',
  7682. border: '1px solid rgba(255, 255, 255, 0.1)',
  7683. color: 'white',
  7684. padding: '8px 12px',
  7685. outline: 'none'
  7686. });
  7687. }
  7688. // Styliser l'input du nom
  7689. if (nameInput instanceof HTMLInputElement) {
  7690. Object.assign(nameInput.style, {
  7691. background: 'rgba(30, 35, 50, 0.8)',
  7692. borderRadius: '8px',
  7693. border: '1px solid rgba(255, 255, 255, 0.1)',
  7694. color: 'white',
  7695. padding: '8px 12px',
  7696. outline: 'none'
  7697. });
  7698. // Focus style
  7699. nameInput.addEventListener('focus', () => {
  7700. nameInput.style.border = '1px solid #4287f5';
  7701. nameInput.style.boxShadow = '0 0 8px rgba(66, 135, 245, 0.5)';
  7702. });
  7703. nameInput.addEventListener('blur', () => {
  7704. nameInput.style.border = '1px solid rgba(255, 255, 255, 0.1)';
  7705. nameInput.style.boxShadow = 'none';
  7706. });
  7707. }
  7708. // Styliser la section d'aide
  7709. if (helpSection) {
  7710. Object.assign(helpSection.style, {
  7711. background: 'rgba(20, 25, 40, 0.7)',
  7712. borderRadius: '8px',
  7713. padding: '15px',
  7714. margin: '15px 0',
  7715. maxHeight: '300px',
  7716. overflowY: 'auto',
  7717. scrollbarWidth: 'thin',
  7718. scrollbarColor: '#4287f5 rgba(25, 25, 35, 0.5)'
  7719. });
  7720. // Style the help section titles
  7721. const helpTitles = helpSection.querySelectorAll('h1');
  7722. helpTitles.forEach(title => {
  7723. if (title instanceof HTMLElement) {
  7724. Object.assign(title.style, {
  7725. color: '#4287f5',
  7726. fontSize: '18px',
  7727. marginTop: '15px',
  7728. marginBottom: '8px'
  7729. });
  7730. }
  7731. });
  7732. // Style the paragraphs
  7733. const helpParagraphs = helpSection.querySelectorAll('p');
  7734. helpParagraphs.forEach(p => {
  7735. if (p instanceof HTMLElement) {
  7736. p.style.color = 'rgba(255, 255, 255, 0.8)';
  7737. p.style.fontSize = '14px';
  7738. p.style.marginBottom = '8px';
  7739. }
  7740. });
  7741. // Style the action terms and controls
  7742. const actionTerms = helpSection.querySelectorAll('.help-action');
  7743. actionTerms.forEach(term => {
  7744. if (term instanceof HTMLElement) {
  7745. term.style.color = '#ffc107'; // Yellow
  7746. term.style.fontWeight = 'bold';
  7747. }
  7748. });
  7749. const controlTerms = helpSection.querySelectorAll('.help-control');
  7750. controlTerms.forEach(term => {
  7751. if (term instanceof HTMLElement) {
  7752. term.style.color = '#4287f5'; // Bleu
  7753. term.style.fontWeight = 'bold';
  7754. }
  7755. });
  7756. }
  7757. // Apply specific style to double buttons
  7758. const btnsDoubleRow = document.querySelector('.btns-double-row');
  7759. if (btnsDoubleRow instanceof HTMLElement) {
  7760. btnsDoubleRow.style.display = 'flex';
  7761. btnsDoubleRow.style.gap = '10px';
  7762. btnsDoubleRow.style.marginTop = '10px';
  7763. }
  7764. }
  7765. MainMenuCleaning() {
  7766. // Déconnecter l'observateur précédent s'il existe
  7767. if (this.adBlockObserver) {
  7768. this.adBlockObserver.disconnect();
  7769. this.adBlockObserver = null;
  7770. }
  7771. // Select elements to hide/show
  7772. const newsWrapper = document.getElementById('news-wrapper');
  7773. const adBlockLeft = document.getElementById('ad-block-left');
  7774. const socialLeft = document.getElementById('social-share-block-wrapper');
  7775. const leftCollun = document.getElementById('left-column');
  7776. const elementsToMonitor = [
  7777. { element: newsWrapper, id: 'news-wrapper' },
  7778. { element: adBlockLeft, id: 'ad-block-left' },
  7779. { element: socialLeft, id: 'social-share-block-wrapper' },
  7780. { element: leftCollun, id: 'left-column' }
  7781. ];
  7782. // Appliquer le style personnalisé au menu principal
  7783. this.applyCustomMainMenuStyle();
  7784. if (this.isMainMenuCleaned) {
  7785. // Clean mode: hide elements
  7786. elementsToMonitor.forEach(item => {
  7787. if (item.element)
  7788. item.element.style.display = 'none';
  7789. });
  7790. // Create an observer to prevent the site from redisplaying elements
  7791. this.adBlockObserver = new MutationObserver((mutations) => {
  7792. let needsUpdate = false;
  7793. mutations.forEach(mutation => {
  7794. if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
  7795. const target = mutation.target;
  7796. // Check if the element is one of those we are monitoring
  7797. if (elementsToMonitor.some(item => item.id === target.id && target.style.display !== 'none')) {
  7798. target.style.display = 'none';
  7799. needsUpdate = true;
  7800. }
  7801. }
  7802. });
  7803. // If the site tries to redisplay an advertising element, we prevent it
  7804. if (needsUpdate) {
  7805. this.logger.log('Detection of attempt to redisplay ads - Forced hiding');
  7806. }
  7807. });
  7808. // Observe style changes on elements
  7809. elementsToMonitor.forEach(item => {
  7810. if (item.element && this.adBlockObserver) {
  7811. this.adBlockObserver.observe(item.element, {
  7812. attributes: true,
  7813. attributeFilter: ['style']
  7814. });
  7815. }
  7816. });
  7817. // Vérifier également le document body pour de nouveaux éléments ajoutés
  7818. const bodyObserver = new MutationObserver(() => {
  7819. // Réappliquer notre nettoyage après un court délai
  7820. setTimeout(() => {
  7821. if (this.isMainMenuCleaned) {
  7822. elementsToMonitor.forEach(item => {
  7823. const element = document.getElementById(item.id);
  7824. if (element && element.style.display !== 'none') {
  7825. element.style.display = 'none';
  7826. }
  7827. });
  7828. }
  7829. }, 100);
  7830. });
  7831. // Observe changes in the DOM
  7832. bodyObserver.observe(document.body, { childList: true, subtree: true });
  7833. }
  7834. else {
  7835. // Mode normal: rétablir l'affichage
  7836. elementsToMonitor.forEach(item => {
  7837. if (item.element)
  7838. item.element.style.display = 'block';
  7839. });
  7840. }
  7841. }
  7842. removeSimpleSpotifyPlayer() {
  7843. // Supprimer le conteneur principal du lecteur
  7844. const container = document.getElementById('spotify-player-container');
  7845. if (container) {
  7846. container.remove();
  7847. }
  7848. // Supprimer aussi le bouton flottant grâce à son ID
  7849. const floatButton = document.getElementById('spotify-float-button');
  7850. if (floatButton) {
  7851. floatButton.remove();
  7852. }
  7853. }
  7854. }
  7855.  
  7856. ;// ./src/HUD/MOD/LoadingScreen.ts
  7857. /**
  7858. * LoadingScreen.ts
  7859. *
  7860. * This module provides a loading animation with a logo and a rotating loading circle
  7861. * that displays during the loading of game resources.
  7862. */
  7863. class LoadingScreen {
  7864. /**
  7865. * Creates a new instance of the loading screen
  7866. * @param logoUrl URL of the Kxs logo to display
  7867. */
  7868. constructor(logoUrl) {
  7869. this.logoUrl = logoUrl;
  7870. this.container = document.createElement('div');
  7871. this.initializeStyles();
  7872. this.createContent();
  7873. }
  7874. /**
  7875. * Initializes CSS styles for the loading screen
  7876. */
  7877. initializeStyles() {
  7878. // Styles for the main container
  7879. Object.assign(this.container.style, {
  7880. position: 'fixed',
  7881. top: '0',
  7882. left: '0',
  7883. width: '100%',
  7884. height: '100%',
  7885. backgroundColor: 'rgba(0, 0, 0, 0.9)',
  7886. display: 'flex',
  7887. flexDirection: 'column',
  7888. justifyContent: 'center',
  7889. alignItems: 'center',
  7890. zIndex: '9999',
  7891. transition: 'opacity 0.5s ease-in-out',
  7892. animation: 'fadeIn 0.5s ease-in-out',
  7893. backdropFilter: 'blur(5px)'
  7894. });
  7895. }
  7896. /**
  7897. * Creates the loading screen content (logo and loading circle)
  7898. */
  7899. createContent() {
  7900. // Create container for the logo
  7901. const logoContainer = document.createElement('div');
  7902. Object.assign(logoContainer.style, {
  7903. width: '200px',
  7904. height: '200px',
  7905. marginBottom: '20px',
  7906. position: 'relative',
  7907. display: 'flex',
  7908. justifyContent: 'center',
  7909. alignItems: 'center'
  7910. });
  7911. // Create the logo element
  7912. const logo = document.createElement('img');
  7913. logo.src = this.logoUrl;
  7914. Object.assign(logo.style, {
  7915. width: '150px',
  7916. height: '150px',
  7917. objectFit: 'contain',
  7918. position: 'absolute',
  7919. zIndex: '2',
  7920. animation: 'pulse 2s ease-in-out infinite'
  7921. });
  7922. // Create the main loading circle
  7923. const loadingCircle = document.createElement('div');
  7924. Object.assign(loadingCircle.style, {
  7925. width: '180px',
  7926. height: '180px',
  7927. border: '4px solid transparent',
  7928. borderTopColor: '#3498db',
  7929. borderRadius: '50%',
  7930. animation: 'spin 1.5s linear infinite',
  7931. position: 'absolute',
  7932. zIndex: '1'
  7933. });
  7934. // Create a second loading circle (rotating in the opposite direction)
  7935. const loadingCircle2 = document.createElement('div');
  7936. Object.assign(loadingCircle2.style, {
  7937. width: '200px',
  7938. height: '200px',
  7939. border: '2px solid transparent',
  7940. borderLeftColor: '#e74c3c',
  7941. borderRightColor: '#e74c3c',
  7942. borderRadius: '50%',
  7943. animation: 'spin-reverse 3s linear infinite',
  7944. position: 'absolute',
  7945. zIndex: '0'
  7946. });
  7947. // Add animations
  7948. const styleSheet = document.createElement('style');
  7949. styleSheet.textContent = `
  7950. @keyframes spin {
  7951. 0% { transform: rotate(0deg); }
  7952. 100% { transform: rotate(360deg); }
  7953. }
  7954. @keyframes spin-reverse {
  7955. 0% { transform: rotate(0deg); }
  7956. 100% { transform: rotate(-360deg); }
  7957. }
  7958. @keyframes pulse {
  7959. 0% { transform: scale(1); }
  7960. 50% { transform: scale(1.05); }
  7961. 100% { transform: scale(1); }
  7962. }
  7963. @keyframes fadeIn {
  7964. 0% { opacity: 0; }
  7965. 100% { opacity: 1; }
  7966. }
  7967. `;
  7968. document.head.appendChild(styleSheet);
  7969. // Ajout d'un texte de chargement
  7970. const loadingText = document.createElement('div');
  7971. loadingText.textContent = 'Loading...';
  7972. Object.assign(loadingText.style, {
  7973. color: 'white',
  7974. fontFamily: 'Arial, sans-serif',
  7975. fontSize: '18px',
  7976. marginTop: '20px',
  7977. animation: 'pulse 1.5s ease-in-out infinite'
  7978. });
  7979. // Ajout d'un sous-texte
  7980. const subText = document.createElement('div');
  7981. subText.textContent = 'Initializing resources...';
  7982. Object.assign(subText.style, {
  7983. color: 'rgba(255, 255, 255, 0.7)',
  7984. fontFamily: 'Arial, sans-serif',
  7985. fontSize: '14px',
  7986. marginTop: '5px'
  7987. });
  7988. // Assemble the elements
  7989. logoContainer.appendChild(loadingCircle2);
  7990. logoContainer.appendChild(loadingCircle);
  7991. logoContainer.appendChild(logo);
  7992. this.container.appendChild(logoContainer);
  7993. this.container.appendChild(loadingText);
  7994. this.container.appendChild(subText);
  7995. }
  7996. /**
  7997. * Shows the loading screen
  7998. */
  7999. show() {
  8000. document.body.appendChild(this.container);
  8001. }
  8002. /**
  8003. * Hides the loading screen with a fade transition
  8004. */
  8005. hide() {
  8006. this.container.style.opacity = '0';
  8007. setTimeout(() => {
  8008. if (this.container.parentNode) {
  8009. document.body.removeChild(this.container);
  8010. }
  8011. }, 500); // Wait for the transition to finish before removing the element
  8012. }
  8013. }
  8014.  
  8015. ;// ./src/index.ts
  8016.  
  8017.  
  8018.  
  8019.  
  8020.  
  8021.  
  8022.  
  8023. intercept("audio/ambient/menu_music_01.mp3", background_song);
  8024. intercept('img/survev_logo_full.png', full_logo);
  8025. const kxsClient = new KxsClient();
  8026. const loadingScreen = new LoadingScreen(kxs_logo);
  8027. loadingScreen.show();
  8028. const backgroundElement = document.getElementById("background");
  8029. if (backgroundElement)
  8030. backgroundElement.style.backgroundImage = `url("${background_image}")`;
  8031. const favicon = document.createElement('link');
  8032. favicon.rel = 'icon';
  8033. favicon.type = 'image/png';
  8034. favicon.href = kxs_logo;
  8035. document.head.appendChild(favicon);
  8036. document.title = "KxsClient";
  8037. const uiStatsLogo = document.querySelector('#ui-stats-logo');
  8038. if (uiStatsLogo) {
  8039. uiStatsLogo.style.backgroundImage = `url('${full_logo}')`;
  8040. }
  8041. const newChangelogUrl = config_namespaceObject.base_url;
  8042. const startBottomMiddle = document.getElementById("start-bottom-middle");
  8043. if (startBottomMiddle) {
  8044. const links = startBottomMiddle.getElementsByTagName("a");
  8045. for (let i = 0; i < links.length; i++) {
  8046. const link = links[i];
  8047. if (link.href.includes("changelogRec.html") || link.href.includes("changelog.html")) {
  8048. link.href = newChangelogUrl;
  8049. link.textContent = package_namespaceObject.rE;
  8050. }
  8051. if (i === 1) {
  8052. link.remove();
  8053. }
  8054. }
  8055. }
  8056. setTimeout(() => {
  8057. loadingScreen.hide();
  8058. }, 1400);
  8059.  
  8060. })();
  8061.  
  8062. /******/ })()
  8063. ;