SilverScripts

Find out prices of items in your inventory by hovering over them while at the Marketplace, in the Inner City, or whilst browsing your Inventory in the Outpost, automatically use services, edit and equip Quickswaps/Loadouts, and more!

  1. // ==UserScript==
  2. // @name SilverScripts
  3. // @namespace http://tampermonkey.net/
  4. // @version 7.9.2
  5. // @description Find out prices of items in your inventory by hovering over them while at the Marketplace, in the Inner City, or whilst browsing your Inventory in the Outpost, automatically use services, edit and equip Quickswaps/Loadouts, and more!
  6. // @author SilverBeam
  7. // @exclude https://fairview.deadfrontier.com/onlinezombiemmo/index.php?action=login2
  8. // @exclude https://fairview.deadfrontier.com/onlinezombiemmo/index.php?action=logout*
  9. // @match https://fairview.deadfrontier.com/onlinezombiemmo/index.php*
  10. // @match https://fairview.deadfrontier.com/onlinezombiemmo/DF3D/DF3D_InventoryPage.php?page=31*
  11. // @match https://fairview.deadfrontier.com/onlinezombiemmo/
  12. // @grant GM.getValue
  13. // @grant GM.setValue
  14. // @grant GM_xmlhttpRequest
  15. // @grant GM.xmlHttpRequest
  16. // @grant GM_openInTab
  17. // @grant GM.openInTab
  18. // @license GPL-3.0-or-later
  19. // ==/UserScript==
  20.  
  21. (function() {
  22. 'use strict';
  23.  
  24. //////////////////////////////
  25. // Variables Declaration //
  26. /////////////////////////////
  27.  
  28. var userVars = unsafeWindow.userVars;
  29. var globalData = unsafeWindow.globalData;
  30. var infoBox = unsafeWindow.infoBox;
  31. var itemsDataBank = {};
  32. var servicesDataBank = {};
  33. var inventoryArray = [];
  34. var loadouts = [];
  35. var pickingLoadoutCategory = "";
  36. var pickingLoadoutSlotIndex = -1;
  37. var activeLoadoutEdit = -1;
  38. var lastSlotHovered = -1;
  39. unsafeWindow.tooltipDisplaying = false;
  40. var userData = {};
  41. var savedMarketData = {
  42. "requestsIssued": 0,
  43. "previousDataTimestamp": 0,
  44. "previousItemTimestamp": {},
  45. "previousServicesTimestamp": 0,
  46. "itemsDataBank": {},
  47. "servicesDataBank": {}
  48. };
  49. var characterCookieData = {};
  50. var lastActiveUserID = null;
  51. var lastQuickSwitcherPage = -1;
  52. var REQUEST_LIMIT = 17;
  53. var userSettings = {
  54. "hoverPrices": true,
  55. "autoService": true,
  56. "autoMarketWithdraw": true,
  57. "alwaysDisplayQuickSwitcher": false,
  58. "innerCityButton": true
  59. }; //Default Settings
  60. var GREASYFORK_SCRIPT_ID = 383308;
  61. var updateCheck = {
  62. "skipVer": "0.0.0",
  63. "lastCheck": "1980-01-01"
  64. };
  65. var uploadedVersion; //This is saved here to avoid calling 2 times the GM library
  66.  
  67. var pendingRequests = {
  68. "requestsNeeded": 0,
  69. "requestsCompleted": 0,
  70. "requesting": false,
  71. "requestsCooldownPeriod": 500, //Minimum time before another refresh is issued again after an inventory change
  72. "requestsCoolingDown": false
  73. };
  74.  
  75. var locations = {
  76. "inventories": [
  77. "fairview.deadfrontier.com/onlinezombiemmo/index.php?page=25",
  78. "fairview.deadfrontier.com/onlinezombiemmo/index.php?page=35",
  79. "fairview.deadfrontier.com/onlinezombiemmo/index.php?page=24",
  80. "fairview.deadfrontier.com/onlinezombiemmo/index.php?page=50",
  81. "fairview.deadfrontier.com/onlinezombiemmo/DF3D/DF3D_InventoryPage.php?page=31",
  82. ],
  83. "marketplace": ["fairview.deadfrontier.com/onlinezombiemmo/index.php?page=35"],
  84. "yard": ["fairview.deadfrontier.com/onlinezombiemmo/index.php?page=24"],
  85. "storage": ["fairview.deadfrontier.com/onlinezombiemmo/index.php?page=50"],
  86. "innerCity": ["fairview.deadfrontier.com/onlinezombiemmo/index.php?page=21"],
  87. "ICInventory": ["fairview.deadfrontier.com/onlinezombiemmo/DF3D/DF3D_InventoryPage.php?page=31"],
  88. "forum": ["fairview.deadfrontier.com/onlinezombiemmo/index.php?board=", "fairview.deadfrontier.com/onlinezombiemmo/index.php?action=forum", "fairview.deadfrontier.com/onlinezombiemmo/index.php?topic="]
  89. };
  90.  
  91. var helpWindow = unsafeWindow.df_prompt;
  92.  
  93. var helpWindowStructure = {
  94. "home": {
  95. "data": [
  96. ["span", "Welcome to SilverScripts Help and Settings!", []],
  97. ["p", " ", []],
  98. ["button", "AutoService Help", [], openHelpWindowPage, ["autoService"]],
  99. ["button", "AutoService not working?", [], openHelpWindowPage, ["serviceReadme"]],
  100. ["button", "MarketWithdraw Help", [], openHelpWindowPage, ["marketWithdraw"]],
  101. ["button", "Settings", [], openHelpWindowPage, ["settings"]],
  102. ["button", "Close", [], closeHelpWindowPage, []]
  103. ],
  104. "style": [
  105. ["height", "145px"]
  106. ]
  107. },
  108. "serviceReadme": {
  109. "data": [
  110. ["span", "Warning!Prices are updated only when something in the inventory changes. If you are unable to purchase a service, move an item in the inventory around to refresh services data!", []],
  111. ["p", "", []],
  112. ["div", "", [],
  113. [
  114. ["button", "Back", {
  115. "style": [
  116. ["position", "absolute"],
  117. ["left", "30px"]
  118. ]
  119. }, openHelpWindowPage, ["home"]],
  120. ["button", "Close", {
  121. "style": [
  122. ["position", "absolute"],
  123. ["right", "30px"]
  124. ]
  125. }, closeHelpWindowPage, []]
  126. ]
  127. ]
  128. ],
  129. "style": [
  130. ["height", "160px"]
  131. ]
  132. },
  133. "autoService": {
  134. "data": [
  135. ["span", "If you hold the <span style='color: #ff0000;'>[ALT]</span> key while hovering on a serviceable item, a prompt will appear. By ALT+Clicking, the relevant service for that item will be automatically bought from the market.", []],
  136. ["p", "", []],
  137. ["div", "", [],
  138. [
  139. ["button", "Back", {
  140. "style": [
  141. ["position", "absolute"],
  142. ["left", "30px"]
  143. ]
  144. }, openHelpWindowPage, ["home"]],
  145. ["button", "Close", {
  146. "style": [
  147. ["position", "absolute"],
  148. ["right", "30px"]
  149. ]
  150. }, closeHelpWindowPage, []]
  151. ]
  152. ]
  153. ],
  154. "style": [
  155. ["height", "170px"]
  156. ]
  157. },
  158. "marketWithdraw": {
  159. "data": [
  160. ["span", "If you don't have enough cash to buy an item, the <span style='color: #ff0000;'>buy</span> button is replaced by a <span style='color: #ff0000;'>withdraw</span> button. By pressing it, the necessary cash will be withdrawn from your bank. The button is disabled if the bank doesn't have enough cash. This function can be disabled in the settings.", []],
  161. ["p", "", []],
  162. ["div", "", [],
  163. [
  164. ["button", "Back", {
  165. "style": [
  166. ["position", "absolute"],
  167. ["left", "30px"]
  168. ]
  169. }, openHelpWindowPage, ["home"]],
  170. ["button", "Close", {
  171. "style": [
  172. ["position", "absolute"],
  173. ["right", "30px"]
  174. ]
  175. }, closeHelpWindowPage, []]
  176. ]
  177. ]
  178. ],
  179. "style": [
  180. ["height", "205px"]
  181. ]
  182. },
  183. "settings": {
  184. "data": [
  185. ["button", "Disable HoverPrices", [], flipSetting, ["hoverPrices", 0]],
  186. ["button", "Disable AutoService", [], flipSetting, ["autoService", 1]],
  187. ["button", "Disable AutoMarketWithdraw", [], flipSetting, ["autoMarketWithdraw", 2]],
  188. ["button", "Enable AlwaysDisplayQuickSwitcher", [], flipSetting, ["alwaysDisplayQuickSwitcher", 3]],
  189. ["button", "Disable InnerCityButton", [], flipSetting, ["innerCityButton", 4]],
  190. ["p", "", []],
  191. ["div", "", [],
  192. [
  193. ["button", "Back", {
  194. "style": [
  195. ["position", "absolute"],
  196. ["left", "30px"]
  197. ]
  198. }, openHelpWindowPage, ["home"]],
  199. ["button", "Close", {
  200. "style": [
  201. ["position", "absolute"],
  202. ["right", "30px"]
  203. ]
  204. }, closeHelpWindowPage, []]
  205. ]
  206. ]
  207. ],
  208. "style": [
  209. ["height", "140px"],
  210. ["width", "300px"]
  211. ]
  212. },
  213. "loadoutsMenu": {
  214. "data": [
  215. ["button", "Equip Quickswap", {
  216. "style": [
  217. ["position", "absolute"],
  218. ["left", "30px"]
  219. ]
  220. }, loadoutOpenEquipMenu, []],
  221. ["button", "Edit Quickswap", {
  222. "style": [
  223. ["position", "absolute"],
  224. ["left", "30px"]
  225. ]
  226. }, loadoutOpenEditMenu, []],
  227. ["button", "Reset Quickswap", {
  228. "style": [
  229. ["position", "absolute"],
  230. ["left", "30px"]
  231. ]
  232. }, loadoutOpenResetMenu, []],
  233. ["button", "Export Quickswaps", {
  234. "style": [
  235. ["position", "absolute"],
  236. ["left", "30px"]
  237. ]
  238. }, exportLoadoutsToJson, []],
  239. ["button", "Import Quickswaps", {
  240. "style": [
  241. ["position", "absolute"],
  242. ["left", "30px"]
  243. ]
  244. }, importLoadoutsFromJson, []],
  245. //["button","Remove Cosmetic Set",{"style":[["position","absolute"],["left","30px"]]},loadoutOpenResetMenu,[]],
  246. ["p", "", []],
  247. ["button", "Close", {
  248. "style": [
  249. ["position", "absolute"],
  250. ["left", "30px"]
  251. ]
  252. }, closeHelpWindowPage, []]
  253. ],
  254. "style": [
  255. ["height", "130px"],
  256. ["width", "300px"]
  257. ]
  258. },
  259. "loadoutsListEquip": {
  260. "data": [
  261. ["div", "", [],
  262. [
  263. ["button", "Equip Quickswap:", {
  264. "style": [
  265. ["position", "absolute"],
  266. ["left", "30px"]
  267. ]
  268. }, equipLoadout, [0]],
  269. ["span", "1", {
  270. "style": [
  271. ["position", "absolute"],
  272. ["right", "30px"]
  273. ]
  274. }]
  275. ]
  276. ],
  277. ["div", "", [],
  278. [
  279. ["button", "Equip Quickswap:", {
  280. "style": [
  281. ["position", "absolute"],
  282. ["left", "30px"]
  283. ]
  284. }, equipLoadout, [1]],
  285. ["span", "2", {
  286. "style": [
  287. ["position", "absolute"],
  288. ["right", "30px"]
  289. ]
  290. }]
  291. ]
  292. ],
  293. ["div", "", [],
  294. [
  295. ["button", "Equip Quickswap:", {
  296. "style": [
  297. ["position", "absolute"],
  298. ["left", "30px"]
  299. ]
  300. }, equipLoadout, [2]],
  301. ["span", "3", {
  302. "style": [
  303. ["position", "absolute"],
  304. ["right", "30px"]
  305. ]
  306. }]
  307. ]
  308. ],
  309. ["div", "", [],
  310. [
  311. ["button", "Equip Quickswap:", {
  312. "style": [
  313. ["position", "absolute"],
  314. ["left", "30px"]
  315. ]
  316. }, equipLoadout, [3]],
  317. ["span", "4", {
  318. "style": [
  319. ["position", "absolute"],
  320. ["right", "30px"]
  321. ]
  322. }]
  323. ]
  324. ],
  325. ["p", "", []],
  326. ["div", "", [],
  327. [
  328. ["button", "Back", {
  329. "style": [
  330. ["position", "absolute"],
  331. ["left", "30px"]
  332. ]
  333. }, openHelpWindowPage, ["loadoutsMenu"]],
  334. ["button", "Close", {
  335. "style": [
  336. ["position", "absolute"],
  337. ["right", "30px"]
  338. ]
  339. }, closeHelpWindowPage, []]
  340. ]
  341. ]
  342. ],
  343. "style": [
  344. ["height", "130px"],
  345. ["width", "300px"]
  346. ]
  347. },
  348. "loadoutsListEdit": {
  349. "data": [
  350. ["div", "", [],
  351. [
  352. ["button", "Edit Quickswap:", {
  353. "style": [
  354. ["position", "absolute"],
  355. ["left", "30px"]
  356. ]
  357. }, editLoadout, [0]],
  358. ["span", "1", {
  359. "style": [
  360. ["position", "absolute"],
  361. ["right", "30px"]
  362. ]
  363. }]
  364. ]
  365. ],
  366. ["div", "", [],
  367. [
  368. ["button", "Edit Quickswap:", {
  369. "style": [
  370. ["position", "absolute"],
  371. ["left", "30px"]
  372. ]
  373. }, editLoadout, [1]],
  374. ["span", "2", {
  375. "style": [
  376. ["position", "absolute"],
  377. ["right", "30px"]
  378. ]
  379. }]
  380. ]
  381. ],
  382. ["div", "", [],
  383. [
  384. ["button", "Edit Quickswap:", {
  385. "style": [
  386. ["position", "absolute"],
  387. ["left", "30px"]
  388. ]
  389. }, editLoadout, [2]],
  390. ["span", "3", {
  391. "style": [
  392. ["position", "absolute"],
  393. ["right", "30px"]
  394. ]
  395. }]
  396. ]
  397. ],
  398. ["div", "", [],
  399. [
  400. ["button", "Edit Quickswap:", {
  401. "style": [
  402. ["position", "absolute"],
  403. ["left", "30px"]
  404. ]
  405. }, editLoadout, [3]],
  406. ["span", "4", {
  407. "style": [
  408. ["position", "absolute"],
  409. ["right", "30px"]
  410. ]
  411. }]
  412. ]
  413. ],
  414. ["p", "", []],
  415. ["div", "", [],
  416. [
  417. ["button", "Back", {
  418. "style": [
  419. ["position", "absolute"],
  420. ["left", "30px"]
  421. ]
  422. }, openHelpWindowPage, ["loadoutsMenu"]],
  423. ["button", "Close", {
  424. "style": [
  425. ["position", "absolute"],
  426. ["right", "30px"]
  427. ]
  428. }, closeHelpWindowPage, []]
  429. ]
  430. ]
  431. ],
  432. "style": [
  433. ["height", "130px"],
  434. ["width", "300px"]
  435. ]
  436. },
  437. "loadoutEdit": {
  438. "data": [
  439. ["p", "", []],
  440. ["div", "", [],
  441. [
  442. ["button", "Quickswap Name:", {
  443. "style": [
  444. ["position", "absolute"],
  445. ["left", "70px"]
  446. ]
  447. }, renameLoadout, []],
  448. ["span", "name", {
  449. "style": [
  450. ["position", "absolute"],
  451. ["right", "70px"]
  452. ]
  453. }]
  454. ]
  455. ],
  456. ["p", "", []],
  457. ["div", "", [],
  458. [
  459. ["span", "Armor", {
  460. "style": [
  461. ["position", "absolute"],
  462. ["left", "100px"]
  463. ]
  464. }],
  465. ["span", "Backpack", {
  466. "style": [
  467. ["position", "absolute"],
  468. ["right", "80px"]
  469. ]
  470. }],
  471. ]
  472. ],
  473. ["div", "", [],
  474. [
  475. ["div", "", [],
  476. [
  477. ["div", "", {
  478. "style": [
  479. ["position", "absolute"],
  480. ["left", "100px"]
  481. ],
  482. "className": "slot validSlot silverScriptsSlot armour",
  483. "id": "armour_0"
  484. },
  485. []
  486. ],
  487. ["div", "", {
  488. "style": [
  489. ["position", "absolute"],
  490. ["right", "100px"]
  491. ],
  492. "className": "slot validSlot silverScriptsSlot backpack",
  493. "id": "backpack_0"
  494. },
  495. []
  496. ],
  497. ]
  498. ],
  499. ]
  500. ],
  501. ["p", "", []],
  502. ["p", "", []],
  503. ["span", "Weapons", {
  504. "style": [
  505. ["position", "absolute"],
  506. ["left", "155px"]
  507. ]
  508. }],
  509. ["div", "", [],
  510. [
  511. ["div", "", {
  512. "style": [
  513. ["position", "absolute"],
  514. ["left", "100px"]
  515. ],
  516. "className": "slot validSlot silverScriptsSlot weapon",
  517. "id": "weapon_0"
  518. },
  519. []
  520. ],
  521. ["div", "", {
  522. "style": [
  523. ["position", "absolute"],
  524. ["left", "160px"]
  525. ],
  526. "className": "slot validSlot silverScriptsSlot weapon",
  527. "id": "weapon_1"
  528. },
  529. []
  530. ],
  531. ["div", "", {
  532. "style": [
  533. ["position", "absolute"],
  534. ["left", "220px"]
  535. ],
  536. "className": "slot validSlot silverScriptsSlot weapon",
  537. "id": "weapon_2"
  538. },
  539. []
  540. ],
  541. ]
  542. ],
  543. ["p", "", []],
  544. ["p", "", []],
  545. ["span", "Implants", {
  546. "style": [
  547. ["position", "absolute"],
  548. ["left", "148px"]
  549. ]
  550. }],
  551. ["div", "", {
  552. "style": [
  553. ["position", "absolute"],
  554. ["width", "100%"],
  555. ["height", "176px"]
  556. ]
  557. },
  558. [
  559. ["div", "", {
  560. "style": [
  561. ["position", "absolute"],
  562. ["left", "100px"],
  563. ["width", "100%"],
  564. ["height", "44px"]
  565. ]
  566. },
  567. [
  568. ["div", "", {
  569. "style": [
  570. ["position", "absolute"],
  571. ["left", "0px"]
  572. ],
  573. "className": "slot validSlot silverScriptsSlot implant",
  574. "id": "implant_0"
  575. },
  576. []
  577. ],
  578. ["div", "", {
  579. "style": [
  580. ["position", "absolute"],
  581. ["left", "44px"]
  582. ],
  583. "className": "slot validSlot silverScriptsSlot implant",
  584. "id": "implant_1"
  585. },
  586. []
  587. ],
  588. ["div", "", {
  589. "style": [
  590. ["position", "absolute"],
  591. ["left", "88px"]
  592. ],
  593. "className": "slot validSlot silverScriptsSlot implant",
  594. "id": "implant_2"
  595. },
  596. []
  597. ],
  598. ["div", "", {
  599. "style": [
  600. ["position", "absolute"],
  601. ["left", "132px"]
  602. ],
  603. "className": "slot validSlot silverScriptsSlot implant",
  604. "id": "implant_3"
  605. },
  606. []
  607. ],
  608. ]
  609. ],
  610. ["div", "", {
  611. "style": [
  612. ["position", "absolute"],
  613. ["left", "100px"],
  614. ["top", "44px"],
  615. ["width", "100%"],
  616. ["height", "44px"]
  617. ]
  618. },
  619. [
  620. ["div", "", {
  621. "style": [
  622. ["position", "absolute"],
  623. ["left", "0px"]
  624. ],
  625. "className": "slot validSlot silverScriptsSlot implant",
  626. "id": "implant_4"
  627. },
  628. []
  629. ],
  630. ["div", "", {
  631. "style": [
  632. ["position", "absolute"],
  633. ["left", "44px"]
  634. ],
  635. "className": "slot validSlot silverScriptsSlot implant",
  636. "id": "implant_5"
  637. },
  638. []
  639. ],
  640. ["div", "", {
  641. "style": [
  642. ["position", "absolute"],
  643. ["left", "88px"]
  644. ],
  645. "className": "slot validSlot silverScriptsSlot implant",
  646. "id": "implant_6"
  647. },
  648. []
  649. ],
  650. ["div", "", {
  651. "style": [
  652. ["position", "absolute"],
  653. ["left", "132px"]
  654. ],
  655. "className": "slot validSlot silverScriptsSlot implant",
  656. "id": "implant_7"
  657. },
  658. []
  659. ],
  660. ]
  661. ],
  662. ["div", "", {
  663. "style": [
  664. ["position", "absolute"],
  665. ["left", "100px"],
  666. ["top", "88px"],
  667. ["width", "100%"],
  668. ["height", "44px"]
  669. ]
  670. },
  671. [
  672. ["div", "", {
  673. "style": [
  674. ["position", "absolute"],
  675. ["left", "0px"]
  676. ],
  677. "className": "slot validSlot silverScriptsSlot implant",
  678. "id": "implant_8"
  679. },
  680. []
  681. ],
  682. ["div", "", {
  683. "style": [
  684. ["position", "absolute"],
  685. ["left", "44px"]
  686. ],
  687. "className": "slot validSlot silverScriptsSlot implant",
  688. "id": "implant_9"
  689. },
  690. []
  691. ],
  692. ["div", "", {
  693. "style": [
  694. ["position", "absolute"],
  695. ["left", "88px"]
  696. ],
  697. "className": "slot validSlot silverScriptsSlot implant",
  698. "id": "implant_10"
  699. },
  700. []
  701. ],
  702. ["div", "", {
  703. "style": [
  704. ["position", "absolute"],
  705. ["left", "132px"]
  706. ],
  707. "className": "slot validSlot silverScriptsSlot implant",
  708. "id": "implant_11"
  709. },
  710. []
  711. ],
  712. ]
  713. ],
  714. ["div", "", {
  715. "style": [
  716. ["position", "absolute"],
  717. ["left", "100px"],
  718. ["top", "132px"],
  719. ["width", "100%"],
  720. ["height", "44px"]
  721. ]
  722. },
  723. [
  724. ["div", "", {
  725. "style": [
  726. ["position", "absolute"],
  727. ["left", "0px"]
  728. ],
  729. "className": "slot validSlot silverScriptsSlot implant",
  730. "id": "implant_12"
  731. },
  732. []
  733. ],
  734. ["div", "", {
  735. "style": [
  736. ["position", "absolute"],
  737. ["left", "44px"]
  738. ],
  739. "className": "slot validSlot silverScriptsSlot implant",
  740. "id": "implant_13"
  741. },
  742. []
  743. ],
  744. ["div", "", {
  745. "style": [
  746. ["position", "absolute"],
  747. ["left", "88px"]
  748. ],
  749. "className": "slot validSlot silverScriptsSlot implant",
  750. "id": "implant_14"
  751. },
  752. []
  753. ],
  754. ["div", "", {
  755. "style": [
  756. ["position", "absolute"],
  757. ["left", "132px"]
  758. ],
  759. "className": "slot validSlot silverScriptsSlot implant",
  760. "id": "implant_15"
  761. },
  762. []
  763. ],
  764. ]
  765. ],
  766. ["div", "", {
  767. "style": [
  768. ["position", "absolute"],
  769. ["left", "100px"],
  770. ["top", "176px"],
  771. ["width", "100%"],
  772. ["height", "44px"]
  773. ]
  774. },
  775. [
  776. ["div", "", {
  777. "style": [
  778. ["position", "absolute"],
  779. ["left", "0px"]
  780. ],
  781. "className": "slot validSlot silverScriptsSlot implant",
  782. "id": "implant_16"
  783. },
  784. []
  785. ],
  786. ["div", "", {
  787. "style": [
  788. ["position", "absolute"],
  789. ["left", "44px"]
  790. ],
  791. "className": "slot validSlot silverScriptsSlot implant",
  792. "id": "implant_17"
  793. },
  794. []
  795. ],
  796. ["div", "", {
  797. "style": [
  798. ["position", "absolute"],
  799. ["left", "88px"]
  800. ],
  801. "className": "slot validSlot silverScriptsSlot implant",
  802. "id": "implant_18"
  803. },
  804. []
  805. ],
  806. ["div", "", {
  807. "style": [
  808. ["position", "absolute"],
  809. ["left", "132px"]
  810. ],
  811. "className": "slot validSlot silverScriptsSlot implant",
  812. "id": "implant_19"
  813. },
  814. []
  815. ],
  816. ]
  817. ],
  818. ]
  819. ],
  820. ["div", "", {
  821. "style": [
  822. ["position", "absolute"],
  823. ["bottom", "20px"],
  824. ["width", "100%"]
  825. ]
  826. },
  827. [
  828. ["button", "Back", {
  829. "style": [
  830. ["position", "absolute"],
  831. ["left", "30px"]
  832. ]
  833. }, loadoutOpenEditMenu, []],
  834. //["button","Use as Cosmetic",{"style":[["position","absolute"],["left","130px"]]},makeLoadoutCosmetic,[]],
  835. ["button", "Equip", {
  836. "style": [
  837. ["position", "absolute"],
  838. ["right", "30px"]
  839. ]
  840. }, equipCurrentLoadout, []],
  841. ]
  842. ]
  843. ],
  844. "style": [
  845. ["height", "500px"],
  846. ["width", "375px"],
  847. ["top", "0px"],
  848. ["left", "125px"]
  849. ]
  850. },
  851. "loadoutsListReset": {
  852. "data": [
  853. ["div", "", [],
  854. [
  855. ["button", "Reset Quickswap:", {
  856. "style": [
  857. ["position", "absolute"],
  858. ["left", "30px"]
  859. ]
  860. }, resetLoadoutButton, [0]],
  861. ["span", "1", {
  862. "style": [
  863. ["position", "absolute"],
  864. ["right", "30px"]
  865. ]
  866. }]
  867. ]
  868. ],
  869. ["div", "", [],
  870. [
  871. ["button", "Reset Quickswap:", {
  872. "style": [
  873. ["position", "absolute"],
  874. ["left", "30px"]
  875. ]
  876. }, resetLoadoutButton, [1]],
  877. ["span", "2", {
  878. "style": [
  879. ["position", "absolute"],
  880. ["right", "30px"]
  881. ]
  882. }]
  883. ]
  884. ],
  885. ["div", "", [],
  886. [
  887. ["button", "Reset Quickswap:", {
  888. "style": [
  889. ["position", "absolute"],
  890. ["left", "30px"]
  891. ]
  892. }, resetLoadoutButton, [2]],
  893. ["span", "3", {
  894. "style": [
  895. ["position", "absolute"],
  896. ["right", "30px"]
  897. ]
  898. }]
  899. ]
  900. ],
  901. ["div", "", [],
  902. [
  903. ["button", "Reset Quickswap:", {
  904. "style": [
  905. ["position", "absolute"],
  906. ["left", "30px"]
  907. ]
  908. }, resetLoadoutButton, [3]],
  909. ["span", "4", {
  910. "style": [
  911. ["position", "absolute"],
  912. ["right", "30px"]
  913. ]
  914. }]
  915. ]
  916. ],
  917. ["p", "", []],
  918. ["div", "", [],
  919. [
  920. ["button", "Back", {
  921. "style": [
  922. ["position", "absolute"],
  923. ["left", "30px"]
  924. ]
  925. }, openHelpWindowPage, ["loadoutsMenu"]],
  926. ["button", "Close", {
  927. "style": [
  928. ["position", "absolute"],
  929. ["right", "30px"]
  930. ]
  931. }, closeHelpWindowPage, []]
  932. ]
  933. ]
  934. ],
  935. "style": [
  936. ["height", "130px"],
  937. ["width", "300px"]
  938. ]
  939. },
  940. "updateAvailable": {
  941. "data": [
  942. ["span", "A new update for <span style='color: #ff0000;'>SilverScripts</span> is available. Please update the script through <span style='color: #ff0000;'>TamperMonkey's</span> interface.", {
  943. "style": [
  944. ["position", "absolute"],
  945. ["left", "20px"],
  946. ["right", "20px"],
  947. ["top", "10px"]
  948. ]
  949. }],
  950. ["p", "", []],
  951. ["button", "Go to install page", {
  952. "style": [
  953. ["position", "absolute"],
  954. ["left", "30px"],
  955. ["bottom", "90px"]
  956. ]
  957. }, openScriptGMPage, []],
  958. ["button", "Show changelog", {
  959. "style": [
  960. ["position", "absolute"],
  961. ["left", "30px"],
  962. ["bottom", "70px"]
  963. ]
  964. }, openHelpWindowPage, ["changelog"]],
  965. ["button", "Remind me later", {
  966. "style": [
  967. ["position", "absolute"],
  968. ["left", "30px"],
  969. ["bottom", "50px"]
  970. ]
  971. }, closeHelpWindowPage, []],
  972. ["button", "Stop reminding me", {
  973. "style": [
  974. ["position", "absolute"],
  975. ["left", "30px"],
  976. ["bottom", "30px"]
  977. ]
  978. }, stopUpdateReminderForCurVer, []],
  979. ],
  980. "style": [
  981. ["height", "230px"],
  982. ["width", "300px"]
  983. ]
  984. },
  985. "changelog": {
  986. "data": [
  987. ["span", "Changelog text here", {}],
  988. ["div", "", {},
  989. [
  990. ["button", "Back", {
  991. "style": [
  992. ["position", "absolute"],
  993. ["left", "30px"]
  994. ]
  995. }, openHelpWindowPage, ["updateAvailable"]],
  996. ["button", "Close", {
  997. "style": [
  998. ["position", "absolute"],
  999. ["right", "30px"]
  1000. ]
  1001. }, closeHelpWindowPage, []]
  1002. ]
  1003. ]
  1004. ],
  1005. "style": [
  1006. ["height", "160px"]
  1007. ]
  1008. },
  1009. }
  1010.  
  1011.  
  1012. //////////////////////////
  1013. // Utility Functions //
  1014. /////////////////////////
  1015.  
  1016. function getItemsDataBank() {
  1017. return itemsDataBank;
  1018. }
  1019. unsafeWindow.getSilverItemsDataBank = getItemsDataBank;
  1020.  
  1021. function capitalizeFirstLetter(string) {
  1022. return string.charAt(0).toUpperCase() + string.slice(1);
  1023. }
  1024.  
  1025. function lowerFirstLetter(string) {
  1026. return string.charAt(0).toLowerCase() + string.slice(1);
  1027. }
  1028.  
  1029. function flipSetting(settingName, settingIndex) {
  1030. var oldValue = userSettings[settingName];
  1031. if (oldValue == true) {
  1032. userSettings[settingName] = false;
  1033. helpWindowStructure["settings"]["data"][settingIndex][1] = "Enable " + capitalizeFirstLetter(settingName);
  1034. } else {
  1035. userSettings[settingName] = true;
  1036. helpWindowStructure["settings"]["data"][settingIndex][1] = "Disable " + capitalizeFirstLetter(settingName);
  1037. }
  1038. GM.setValue("userSettings", JSON.stringify(userSettings));
  1039. //Trick to refresh the menu
  1040. openHelpWindowPage("settings");
  1041. }
  1042.  
  1043. function refreshMarketSearch() {
  1044. var itemDisplay = document.getElementById("itemDisplay");
  1045. itemDisplay.scrollTop = 0;
  1046. itemDisplay.scrollLeft = 0;
  1047. unsafeWindow.search();
  1048. }
  1049.  
  1050. function serializeObject(obj) {
  1051. var pairs = [];
  1052. for (var prop in obj) {
  1053. if (!obj.hasOwnProperty(prop)) {
  1054. continue;
  1055. }
  1056. pairs.push(prop + '=' + obj[prop]);
  1057. }
  1058. return pairs.join('&');
  1059. }
  1060.  
  1061. function makeRequest(requestUrl, requestParams, callbackFunc, callBackParams) {
  1062.  
  1063. requestParams["pagetime"] = userVars["pagetime"];
  1064. requestParams["templateID"] = "0";
  1065. requestParams["sc"] = userVars["sc"];
  1066. requestParams["gv"] = 42;
  1067. requestParams["userID"] = userVars["userID"];
  1068. requestParams["password"] = userVars["password"];
  1069.  
  1070. return new Promise((resolve) => {
  1071. var xhttp = new XMLHttpRequest();
  1072. var payload = null;
  1073. xhttp.onreadystatechange = function() {
  1074. if (this.readyState == 4 && this.status == 200) {
  1075. //Invoke the callback with the request response text and some parameters, if any were supplied
  1076. //then resolve the Promise with the callback's reponse
  1077. let callbackResponse = null;
  1078. if (callbackFunc != null) {
  1079. callbackResponse = callbackFunc(this.responseText, callBackParams);
  1080. }
  1081. if (callbackResponse == null) {
  1082. callbackResponse = true;
  1083. }
  1084. resolve(callbackResponse);
  1085. }
  1086. };
  1087.  
  1088. payload = serializeObject(requestParams);
  1089.  
  1090. xhttp.open("POST", requestUrl, true);
  1091. xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  1092. xhttp.setRequestHeader("x-requested-with", "SilverScriptRequest");
  1093. payload = "hash=" + unsafeWindow.hash(payload) + "&" + payload;
  1094. xhttp.send(payload);
  1095. });
  1096. }
  1097.  
  1098. function cloneObject(object) {
  1099. return JSON.parse(JSON.stringify(object));
  1100. }
  1101.  
  1102. function getImplantRestrictions(itemFlashType) {
  1103. //Strip possible extra data from the implant
  1104. var itemBaseFlashType = itemFlashType.split("_")[0];
  1105. //Fetch info on the target implant
  1106. var restrictions = {
  1107. "forbiddenImplants": [],
  1108. "isUnique": false
  1109. };
  1110. if (globalData[itemBaseFlashType]["implant_block"]) {
  1111. restrictions["forbiddenImplants"] = globalData[itemBaseFlashType]["implant_block"].split(',');
  1112. }
  1113.  
  1114. if (globalData[itemBaseFlashType]["implant_unique"] && globalData[itemBaseFlashType]["implant_unique"] == "1") {
  1115. restrictions["isUnique"] = true;
  1116. }
  1117. return restrictions;
  1118. }
  1119.  
  1120. function secondsToHms(d) {
  1121. d = parseInt(d);
  1122. var h = Math.floor(d / 3600);
  1123. var m = Math.floor(d % 3600 / 60);
  1124. var s = Math.floor(d % 3600 % 60);
  1125.  
  1126. var hDisplay = h < 10 ? "0" + h : h;
  1127. var mDisplay = m < 10 ? "0" + m : m;
  1128. var sDisplay = s < 10 ? "0" + s : s;
  1129. return hDisplay + ":" + mDisplay + ":" + sDisplay;
  1130. }
  1131.  
  1132. function isAtLocation(location) {
  1133. //Make an exception check for the homepage as its address is contained in each one
  1134. if (location == "home") {
  1135. if (window.location.href.split("fairview.deadfrontier.com/onlinezombiemmo/index.php")[1] == "" ||
  1136. window.location.href.split("fairview.deadfrontier.com/onlinezombiemmo/")[1] == "") {
  1137. return true;
  1138. } else {
  1139. return false;
  1140. }
  1141. }
  1142. //Check if location name exists first
  1143. if (locations[location] != undefined) {
  1144. for (var i = 0; i < locations[location].length; i++) {
  1145. if (window.location.href.indexOf(locations[location][i]) != -1) {
  1146. return true;
  1147. }
  1148. }
  1149. }
  1150. return false;
  1151. }
  1152.  
  1153. //Old version of the history func from GreasyFork API: https://greasyfork.org/scripts/445697/code/index.js?version=1055427
  1154. async function getScriptHistory(scriptId) {
  1155. return fetch(`https://greasyfork.org/scripts/${scriptId}/versions`).then((r) =>
  1156. r.text()
  1157. ).then((c) => {
  1158. let parser = new DOMParser();
  1159. let list = parser.parseFromString(c, 'text/html').querySelectorAll('.history_versions li'),
  1160. result = []
  1161. for (let i of list) {
  1162. let ver = i.children[1].children[0],
  1163. time = i.children[2],
  1164. log = i.children[3]
  1165.  
  1166. result.push({
  1167. version: {
  1168. id: +(ver.href.match(/\?version=\d.+/)[0].replace('?version=', '')),
  1169. text: ver.innerText,
  1170. url: ver.href
  1171. },
  1172. time: {
  1173. text: time.innerText,
  1174. iso: time.attributes.datetime.value
  1175. },
  1176. changelog: log ? {
  1177. html: log.innerHTML,
  1178. text: log.innerText
  1179. } : null
  1180. })
  1181. }
  1182. return result
  1183. })
  1184. }
  1185.  
  1186. function getLatestScriptVersionFromHistory(history) {
  1187. //The fetched history is already sorted from newest to oldest
  1188. //but every version is prepended by a "v", so cut it
  1189. return history[0].version.text.substring(1);
  1190. }
  1191.  
  1192. async function checkForScriptUpdate() {
  1193. if (isAtLocation("ICInventory") || isAtLocation("forum")) {
  1194. return;
  1195. }
  1196.  
  1197. updateCheck = JSON.parse(await GM.getValue("updateCheck", JSON.stringify(updateCheck)));
  1198. var lastCheckDate = new Date(updateCheck["lastCheck"]);
  1199. var daysElapsedSinceLastCheck = Math.ceil((Date.now() - lastCheckDate) / (1000 * 60 * 60 * 24));
  1200. if (daysElapsedSinceLastCheck <= 1) {
  1201. return;
  1202. }
  1203.  
  1204. //Get script history and local/uploaded script versions
  1205. var history = await getScriptHistory(GREASYFORK_SCRIPT_ID);
  1206. uploadedVersion = getLatestScriptVersionFromHistory(history);
  1207. var localVersion = GM.info.script.version;
  1208.  
  1209. //Check if local version is lesser than uploaded version
  1210. var needsUpdate = uploadedVersion.localeCompare(localVersion, undefined, {
  1211. numeric: true,
  1212. sensitivity: 'base'
  1213. }) == 1;
  1214. //Check if the user has asked to skip or remind later, and that a day has elapsed
  1215. var isVersionAfterSkip = uploadedVersion.localeCompare(updateCheck["skipVer"], undefined, {
  1216. numeric: true,
  1217. sensitivity: 'base'
  1218. }) == 1;
  1219.  
  1220. if (needsUpdate && isVersionAfterSkip) {
  1221. //Save the new version changelog into its menu page
  1222. helpWindowStructure["changelog"]["data"][0][1] = history[0]["changelog"]["html"].trim();
  1223. //Adjust menu height depending on changelog length. Simulate its height when open.
  1224. helpWindow.style.position = "absolute";
  1225. helpWindow.style.left = "208px";
  1226. helpWindow.style.top = "195px";
  1227. helpWindow.style.width = "270px";
  1228. helpWindow.style.height = "100px";
  1229. helpWindow.style.opacity = "0";
  1230. helpWindow.parentNode.style.display = "block";
  1231. helpWindow.innerHTML = "<span id='testSpan'>" + helpWindowStructure["changelog"]["data"][0][1] + "</span>";
  1232. var helpWindowHeight = document.getElementById("testSpan").offsetHeight;
  1233. helpWindowStructure["changelog"]["style"][0][1] = (65 + helpWindowHeight) + "px";
  1234. helpWindow.style.opacity = "1";
  1235. closeHelpWindowPage();
  1236.  
  1237. openHelpWindowPage("updateAvailable");
  1238. }
  1239.  
  1240. //Save current date, we already checked for the day
  1241. updateCheck["lastCheck"] = Date.now();
  1242. GM.setValue("updateCheck", JSON.stringify(updateCheck));
  1243.  
  1244. }
  1245.  
  1246. function openScriptGMPage() {
  1247. window.open(`https://greasyfork.org/scripts/${GREASYFORK_SCRIPT_ID}/code/source.user.js`)
  1248. }
  1249.  
  1250. function stopUpdateReminderForCurVer() {
  1251. //Save the uploaded version as the version to ignore when performing an update check
  1252. updateCheck["skipVer"] = uploadedVersion;
  1253. GM.setValue("updateCheck", JSON.stringify(updateCheck));
  1254.  
  1255. closeHelpWindowPage();
  1256. }
  1257.  
  1258. function updateInventoryData(inventoryData) {
  1259. unsafeWindow.updateIntoArr(unsafeWindow.flshToArr(inventoryData, "DFSTATS_"), unsafeWindow.userVars);
  1260. unsafeWindow.populateInventory();
  1261. unsafeWindow.populateCharacterInventory();
  1262. unsafeWindow.updateAllFields();
  1263. initInventoryArray();
  1264. }
  1265.  
  1266. function getLevelAppropriateMedicalTypeList() {
  1267.  
  1268. var playerLevel = userVars["DFSTATS_df_level"];
  1269.  
  1270. //Chain IFs instead of using Switch bacause that is the ugly but recommended way
  1271. if (playerLevel < 11) {
  1272. return ["steristrips", "plasters"]
  1273. }
  1274. if (playerLevel >= 11 && playerLevel < 21) {
  1275. return ["antisepticspray", "antibiotics"]
  1276. }
  1277. if (playerLevel >= 21 && playerLevel < 31) {
  1278. return ["bandages"]
  1279. }
  1280. if (playerLevel >= 31 && playerLevel < 41) {
  1281. return ["morphine"]
  1282. }
  1283. if (playerLevel >= 41 && playerLevel < 71) {
  1284. return ["nerotonin"]
  1285. }
  1286. if (playerLevel >= 71) {
  1287. return ["nerotonin8b", "steroids"]
  1288. }
  1289. return [];
  1290. }
  1291.  
  1292. function getBestLevelAppropriateMedicalTypeAndAdministerNeed() {
  1293.  
  1294. var damagePercentage = (userVars["DFSTATS_df_hpmax"] / userVars["DFSTATS_df_hpcurrent"]) * 100;
  1295. var medList = getLevelAppropriateMedicalTypeList();
  1296. var chosenMed = "";
  1297. var needAdminister = true;
  1298. var medType, medItem;
  1299.  
  1300. //Use the fact that the array is already sorted
  1301. for (medType of medList) {
  1302. medItem = globalData[medType];
  1303. medItem["healthrestore"] = parseInt(medItem["healthrestore"]);
  1304. medItem["adminhealthrestore"] = medItem["healthrestore"] * 3;
  1305.  
  1306. //Swap to cheaper med if it still lets the player reach max health
  1307. if (chosenMed != "") {
  1308. if (medItem["adminhealthrestore"] >= damagePercentage) {
  1309. chosenMed = medType;
  1310. } else {
  1311. break;
  1312. }
  1313. } else {
  1314. chosenMed = medType;
  1315. }
  1316. }
  1317.  
  1318. //Check the array again to see if we can skip administer. This is ugly but should work.
  1319. for (medType of medList) {
  1320. medItem = cloneObject(globalData[medType]);
  1321.  
  1322. //Swap to cheaper med if it still lets the player reach max health
  1323. if (medItem["healthrestore"] >= damagePercentage) {
  1324. chosenMed = medType;
  1325. needAdminister = false;
  1326. } else {
  1327. break;
  1328. }
  1329. }
  1330.  
  1331. return [chosenMed, needAdminister];
  1332.  
  1333. }
  1334. unsafeWindow.getBestLevelAppropriateMedicalTypeAndAdministerNeed = getBestLevelAppropriateMedicalTypeAndAdministerNeed;
  1335.  
  1336. function getLevelAppropriateFoodTypeList() {
  1337.  
  1338. var playerLevel = userVars["DFSTATS_df_level"];
  1339.  
  1340. //Chain IFs instead of using Switch bacause that is the ugly but recommended way
  1341. //All list are already sorted in descending foodrestore values
  1342. if (playerLevel < 11) {
  1343. return ["millet_cooked", "beer"]
  1344. }
  1345. if (playerLevel >= 11 && playerLevel < 21) {
  1346. return ["hotdogs_cooked", "bakedbeans_cooked"]
  1347. }
  1348. if (playerLevel >= 21 && playerLevel < 31) {
  1349. return ["potatoes_cooked", "tuna_cooked"]
  1350. }
  1351. if (playerLevel >= 31 && playerLevel < 41) {
  1352. return ["eggs_cooked", "salmon_cooked", "oats_cooked"]
  1353. }
  1354. if (playerLevel >= 41 && playerLevel < 71) {
  1355. return ["caviar_cooked", "mixednuts_cooked", "redwine"]
  1356. }
  1357. if (playerLevel >= 71) {
  1358. return ["driedtruffles_cooked", "whiskey"]
  1359. }
  1360. return [];
  1361. }
  1362.  
  1363. function getBestLevelAppropriateFoodType() {
  1364.  
  1365. var hunger = 100 - userVars['DFSTATS_df_hungerhp'];
  1366. var foodList = getLevelAppropriateFoodTypeList();
  1367. var chosenFood = "";
  1368.  
  1369. //Use the fact that the array is already sorted
  1370. for (var foodType of foodList) {
  1371. //Fix cooked items restore levels
  1372. var [id, extraInfo] = foodType.split("_");
  1373. var foodItem = globalData[id];
  1374. if (extraInfo != undefined) {
  1375. foodItem["actual_foodrestore"] = parseInt(foodItem["foodrestore"]) * 3;
  1376. } else {
  1377. foodItem["actual_foodrestore"] = parseInt(foodItem["foodrestore"]);
  1378. }
  1379.  
  1380. //Swap to cheaper food if it still lets the player reach max nourishment
  1381. if (chosenFood != "") {
  1382. if (foodItem["actual_foodrestore"] >= hunger) {
  1383. chosenFood = foodType;
  1384. } else {
  1385. break;
  1386. }
  1387. } else {
  1388. chosenFood = foodType;
  1389. }
  1390. }
  1391.  
  1392. return chosenFood;
  1393.  
  1394. }
  1395.  
  1396. function findLastEmptyGenericSlot(slotType) {
  1397. for (var i = userVars["DFSTATS_df_" + slotType + "slots"]; i >= 1; i--) {
  1398. if (userVars["DFSTATS_df_" + slotType + i + "_type"] === "") {
  1399. return i;
  1400. }
  1401. }
  1402. return false;
  1403. }
  1404.  
  1405. function addPendingRequest() {
  1406. pendingRequests.requestsNeeded += 1;
  1407. pendingRequests.requesting = true;
  1408. }
  1409.  
  1410. function completePendingRequest() {
  1411. pendingRequests.requestsCompleted += 1;
  1412. if (pendingRequests.requestsCompleted >= pendingRequests.requestsNeeded) {
  1413. resetPendingRequests();
  1414. }
  1415. }
  1416.  
  1417. function resetPendingRequests() {
  1418. pendingRequests.requestsNeeded = 0;
  1419. pendingRequests.requestsCompleted = 0;
  1420. pendingRequests.requesting = false;
  1421. }
  1422.  
  1423. function havePendingRequestsCompleted() {
  1424. return !pendingRequests.requesting;
  1425. }
  1426.  
  1427. //////////////////////
  1428. // Init Functions //
  1429. /////////////////////
  1430.  
  1431. function initUserData() {
  1432. if (isAtLocation("forum") || unsafeWindow.userVars == undefined) {
  1433. return;
  1434. }
  1435. if (isAtLocation("ICInventory")) {
  1436. userData["tradezone"] = '22';
  1437. } else {
  1438. userData["tradezone"] = userVars["DFSTATS_df_tradezone"];
  1439. }
  1440.  
  1441. lastActiveUserID = unsafeWindow.userVars['userID'];
  1442. GM.setValue("lastActiveUserID", lastActiveUserID);
  1443. }
  1444.  
  1445. function addItemToDatabank(flashType, quantity) {
  1446.  
  1447. //Init a new inventory item
  1448. var item = {};
  1449. item.id = flashType;
  1450. item.extraInfo = "";
  1451. item.type = "";
  1452. item.flashType = flashType;
  1453. item.trades = [];
  1454.  
  1455. //Check if slot isn't empty
  1456. if (item.id != "" && item.id != undefined) {
  1457. //Detect extra data such as cooked/dye color
  1458. if (item.id.indexOf("_") != -1) {
  1459. item.extraInfo = capitalizeFirstLetter(item.id.split("_")[1]);
  1460. item.id = item.id.split("_")[0];
  1461. }
  1462.  
  1463. var itemGlobData = globalData[item.id];
  1464.  
  1465. //Set shared data across all item types
  1466. item.name = itemGlobData.name;
  1467. item.quantity = quantity;
  1468. item.quantity = item.quantity < 1 ? 1 : item.quantity;
  1469. item.type = capitalizeFirstLetter(itemGlobData.itemtype);
  1470. item.notTransferable = itemGlobData.no_transfer == 1;
  1471.  
  1472. if (item.type == "Armour") {
  1473. //Quantity is current armor HP
  1474. item.maxHP = parseInt(itemGlobData.hp); //Max armor hp
  1475. item.level = itemGlobData.shop_level;
  1476. item.profession = "Engineer";
  1477. item.serviceAction = "buyrepair";
  1478. item.serviceSound = "repair";
  1479. item.serviceTooltip = "Repair";
  1480. item.scrapValue = unsafeWindow.scrapValue(item.flashType, 1);
  1481. }
  1482.  
  1483. if (item.type == "Weapon") {
  1484. item.scrapValue = unsafeWindow.scrapValue(item.flashType, 1);
  1485. }
  1486.  
  1487. if (item.type == "Item") {
  1488. //Add level to the item if it has one
  1489. if (itemGlobData.level != undefined) {
  1490. item.level = itemGlobData.level;
  1491. }
  1492.  
  1493. //Add scrap value if this is a cosmetic
  1494. if (itemGlobData.clothingtype != undefined) {
  1495. item.scrapValue = unsafeWindow.scrapValue(item.flashType, 1);
  1496. }
  1497.  
  1498. //Find if the item has a profession associated and/or is cookable and add it
  1499. //to the databank for a market request
  1500. if (itemGlobData.needcook == "1" && item.extraInfo != "Cooked") {
  1501. item.type = "Cookable";
  1502. item.profession = "Chef";
  1503. item.serviceAction = "buycook";
  1504. item.serviceSound = "cook";
  1505. item.serviceTooltip = "Cook";
  1506. //Add Cooked item info to the Databank
  1507. //If this is the first time this item has been found in the inventory,
  1508. //register its Cooked info into the itemsDataBank
  1509. if (itemsDataBank[item.id + "_cooked"] == null) {
  1510. var cookedItem = {};
  1511. cookedItem.id = item.id + "_cooked";
  1512. cookedItem.extraInfo = "Cooked";
  1513. cookedItem.name = "Cooked " + item.name;
  1514. cookedItem.quantity = 1;
  1515. cookedItem.type = "Item";
  1516. itemsDataBank[cookedItem.id] = cookedItem;
  1517. }
  1518. } else if (itemGlobData.needdoctor == "1") {
  1519. item.type = "Medical";
  1520. item.profession = "Doctor";
  1521. item.serviceAction = "buyadminister";
  1522. item.serviceSound = "heal";
  1523. item.serviceTooltip = "Administer";
  1524. }
  1525. }
  1526.  
  1527. //Fix for cooked items detection
  1528. if (item.extraInfo == "Cooked") {
  1529. item.id = item.id + "_cooked";
  1530. item.name = "Cooked " + item.name;
  1531. }
  1532.  
  1533. //Add profession level required to service the item.
  1534. //If item isn't serviceable this is ignored.
  1535. if (item.level != undefined) {
  1536. item.professionLevel = item.level - 5;
  1537. }
  1538.  
  1539. //If this is the first time this item has been found in the inventory,
  1540. //register its info into the itemsDataBank
  1541. if (itemsDataBank[item.id] == null) {
  1542. itemsDataBank[item.id] = item;
  1543. }
  1544. } else {
  1545. item.name = "";
  1546. item.quantity = 0;
  1547. }
  1548.  
  1549. return item
  1550.  
  1551. }
  1552.  
  1553. function initInventoryArray() {
  1554. if (!isAtLocation("inventories")) {
  1555. return;
  1556. }
  1557.  
  1558. //Reset inventory array
  1559. inventoryArray = [];
  1560.  
  1561. //Refresh databank and inventory contents
  1562. for (var i = 1; i <= parseInt(userVars.DFSTATS_df_invslots); i++) {
  1563.  
  1564. var item = addItemToDatabank(userVars["DFSTATS_df_inv" + i + "_type"],
  1565. parseInt(userVars["DFSTATS_df_inv" + i + "_quantity"]));
  1566.  
  1567. inventoryArray.push(item);
  1568. }
  1569. }
  1570.  
  1571. async function loadStoredSettings() {
  1572. //We stringify the default object as fallback
  1573. userSettings = JSON.parse(await GM.getValue("userSettings", JSON.stringify(userSettings)));
  1574. if (userSettings.hoverPrices == false) {
  1575. helpWindowStructure["settings"]["data"][0][1] = "Enable HoverPrices";
  1576. }
  1577. if (userSettings.autoService == false) {
  1578. helpWindowStructure["settings"]["data"][1][1] = "Enable AutoService";
  1579. }
  1580. if (userSettings.autoMarketWithdraw == false) {
  1581. helpWindowStructure["settings"]["data"][2][1] = "Enable AutoMarketWithdraw";
  1582. }
  1583. if (userSettings.alwaysDisplayQuickSwitcher == true) {
  1584. helpWindowStructure["settings"]["data"][3][1] = "Disable AlwaysDisplayQuickSwitcher";
  1585. }
  1586. if (userSettings.innerCityButton == false) {
  1587. helpWindowStructure["settings"]["data"][4][1] = "Enable InnerCityButton";
  1588. }
  1589. }
  1590.  
  1591. async function initLoadouts() {
  1592. for (var i = 0; i < 4; i++) {
  1593. resetLoadout(i);
  1594. }
  1595.  
  1596. //Load stored loadouts
  1597. loadouts = JSON.parse(await GM.getValue("loadouts", JSON.stringify(loadouts)));
  1598.  
  1599. //Fix for previous versions
  1600. for (let i = 0; i < loadouts.length; i++) {
  1601. if (loadouts[i]["backpack"] == undefined) {
  1602. loadouts[i]["backpack"] = [{
  1603. "characterSlotType": "DFSTATS_df_backpack",
  1604. "characterSlotNumber": 35,
  1605. "storageSlot": -1,
  1606. "itemFlashType": "",
  1607. "itemCategory": ""
  1608. }];
  1609. }
  1610. if (loadouts[i]["hat"] == undefined) {
  1611. loadouts[i]["hat"] = [{
  1612. "characterSlotType": "DFSTATS_df_avatar_hat",
  1613. "characterSlotNumber": 40,
  1614. "storageSlot": -1,
  1615. "itemFlashType": "",
  1616. "itemCategory": ""
  1617. }];
  1618. }
  1619. if (loadouts[i]["mask"] == undefined) {
  1620. loadouts[i]["mask"] = [{
  1621. "characterSlotType": "DFSTATS_df_avatar_mask",
  1622. "characterSlotNumber": 39,
  1623. "storageSlot": -1,
  1624. "itemFlashType": "",
  1625. "itemCategory": ""
  1626. }];
  1627. }
  1628. if (loadouts[i]["coat"] == undefined) {
  1629. loadouts[i]["coat"] = [{
  1630. "characterSlotType": "DFSTATS_df_avatar_coat",
  1631. "characterSlotNumber": 38,
  1632. "storageSlot": -1,
  1633. "itemFlashType": "",
  1634. "itemCategory": ""
  1635. }];
  1636. }
  1637. if (loadouts[i]["shirt"] == undefined) {
  1638. loadouts[i]["shirt"] = [{
  1639. "characterSlotType": "DFSTATS_df_avatar_shirt",
  1640. "characterSlotNumber": 36,
  1641. "storageSlot": -1,
  1642. "itemFlashType": "",
  1643. "itemCategory": ""
  1644. }];
  1645. }
  1646. if (loadouts[i]["trousers"] == undefined) {
  1647. loadouts[i]["trousers"] = [{
  1648. "characterSlotType": "DFSTATS_df_avatar_trousers",
  1649. "characterSlotNumber": 37,
  1650. "storageSlot": -1,
  1651. "itemFlashType": "",
  1652. "itemCategory": ""
  1653. }];
  1654. }
  1655. }
  1656.  
  1657. loadoutRefreshUsedSlotOverlay();
  1658.  
  1659. }
  1660.  
  1661. async function loadSavedMarketData() {
  1662. savedMarketData = JSON.parse(await GM.getValue("savedMarketData", JSON.stringify(savedMarketData)));
  1663. if (savedMarketData["previousItemTimestamp"] == undefined) {
  1664. savedMarketData["previousItemTimestamp"] = {};
  1665. }
  1666. itemsDataBank = savedMarketData["itemsDataBank"];
  1667. servicesDataBank = savedMarketData["servicesDataBank"];
  1668. }
  1669.  
  1670. async function loadStoredCharacterCookieData() {
  1671. //Load stored cookie data
  1672. characterCookieData = JSON.parse(await GM.getValue("characterCookieData", JSON.stringify(characterCookieData)));
  1673. //Fix character cookie if loading from previous versions
  1674. for (let userID in characterCookieData) {
  1675. if (characterCookieData[userID]['userID'] == undefined) {
  1676. characterCookieData[userID]['userID'] = userID;
  1677. }
  1678. }
  1679. //Stop here if outside the home page due to the fact that userVars may not be available
  1680. if (!isAtLocation("home")) {
  1681. return;
  1682. }
  1683. //Update current character cookie
  1684. let characterName = "";
  1685. if (characterCookieData[unsafeWindow.userVars['userID']] != undefined) {
  1686. characterName = characterCookieData[unsafeWindow.userVars['userID']].characterName;
  1687. } else {
  1688. characterName = document.getElementById("sidebar").children[2].firstChild.textContent;
  1689. }
  1690. characterCookieData[unsafeWindow.userVars['userID']] = {
  1691. "characterName": characterName,
  1692. "cookie": document.cookie,
  1693. "userID": unsafeWindow.userVars['userID']
  1694. };
  1695. //Save updated cookie data
  1696. GM.setValue("characterCookieData", JSON.stringify(characterCookieData));
  1697. }
  1698.  
  1699. function removeCharacterFromCharacterCookieData(userID) {
  1700. if (characterCookieData[userID] != undefined) {
  1701. delete characterCookieData[userID];
  1702. //Save updated cookie data
  1703. GM.setValue("characterCookieData", JSON.stringify(characterCookieData));
  1704. }
  1705. }
  1706.  
  1707. function changeCharacterCookieDataName(userID) {
  1708. if (characterCookieData[userID] != undefined) {
  1709. let newCharName = window.prompt("Input the new name for the saved character");
  1710. characterCookieData[userID]["characterName"] = newCharName.slice(0, 16);
  1711. //Save updated cookie data
  1712. GM.setValue("characterCookieData", JSON.stringify(characterCookieData));
  1713. }
  1714. }
  1715.  
  1716. async function loadLastActiveUserID() {
  1717. lastActiveUserID = await GM.getValue("lastActiveUserID", null);
  1718. }
  1719.  
  1720. //////////////////////////////
  1721. // Item Price Functions //
  1722. /////////////////////////////
  1723.  
  1724. function resetDataBankItemsMarketInfo() {
  1725. if (isAtLocation("forum")) {
  1726. return;
  1727. }
  1728.  
  1729. for (var itemName in itemsDataBank) {
  1730. itemsDataBank[itemName].rawServerResponse = "";
  1731. }
  1732.  
  1733. }
  1734.  
  1735. function addAllAmmoToDatabank() {
  1736. if (!isAtLocation("inventories")) {
  1737. return;
  1738. }
  1739. //This array was extracted from the game data using the following one-liner:
  1740. //Object.keys(Object.fromEntries(Object.entries(globalData).filter(([key, value]) => value.itemcat === 'ammo')));
  1741. var ammoTypes = ['32ammo', '35ammo', '357ammo', '38ammo', '40ammo', '45ammo', '50ammo', '55ammo', '20gaugeammo', '16gaugeammo', '12gaugeammo', '10gaugeammo', 'grenadeammo', 'heavygrenadeammo', '55rifleammo', '75rifleammo', '9rifleammo', '127rifleammo', '14rifleammo', 'fuelammo'];
  1742.  
  1743. for (let ammoId of ammoTypes) {
  1744. addItemToDatabank(ammoId, 1);
  1745. }
  1746.  
  1747. }
  1748.  
  1749. function addLevelAppropriateMedicalToDatabank() {
  1750. if (!isAtLocation("marketplace")) {
  1751. return;
  1752. }
  1753.  
  1754. //Add medical to databank
  1755. var [medicalId, needsAdminister] = getBestLevelAppropriateMedicalTypeAndAdministerNeed();
  1756. addItemToDatabank(medicalId, 1);
  1757.  
  1758. //Fetch the medical's price if the player is damaged
  1759. if (parseInt(userVars["DFSTATS_df_hpcurrent"]) < parseInt(userVars["DFSTATS_df_hpmax"])) {
  1760. requestItem(itemsDataBank[medicalId]);
  1761. }
  1762.  
  1763. }
  1764.  
  1765. function addLevelAppropriateFoodToDatabank() {
  1766. if (!isAtLocation("marketplace")) {
  1767. return;
  1768. }
  1769.  
  1770. //Add food to databank
  1771. var foodId = getBestLevelAppropriateFoodType();
  1772. addItemToDatabank(foodId, 1);
  1773.  
  1774. //Fetch the food's price if the player is hungry
  1775. if (parseInt(userVars['DFSTATS_df_hungerhp']) < 100) {
  1776. requestItem(itemsDataBank[foodId]);
  1777. }
  1778.  
  1779. }
  1780.  
  1781. function requestItem(dataBankItem) {
  1782. //Check that we are not exceeding the rate limit
  1783. if (Date.now() < savedMarketData["previousDataTimestamp"] + 30000) {
  1784. //Give priority to not previously fetched results
  1785. if (savedMarketData["previousItemTimestamp"][dataBankItem.id] != undefined &&
  1786. Date.now() < savedMarketData["previousItemTimestamp"][dataBankItem.id] + 30000 &&
  1787. dataBankItem.rawServerResponse != undefined &&
  1788. dataBankItem.rawServerResponse != ""
  1789. ) {
  1790. updateInventoryItemPrices(dataBankItem);
  1791. fillHoverBox();
  1792. return true;
  1793. }
  1794. //If there are any available requests, use them.
  1795. //Otherwise return false as a failstate
  1796. if (savedMarketData["requestsIssued"] < REQUEST_LIMIT) {
  1797. savedMarketData["requestsIssued"] += 1;
  1798. } else {
  1799. //dataBankItem.rawServerResponse = "";
  1800. return false;
  1801. }
  1802. } else {
  1803. //Update the time to NOW if it is the first request
  1804. //after the 30 sec rate limit interval
  1805. savedMarketData["previousDataTimestamp"] = Date.now();
  1806. savedMarketData["requestsIssued"] = 0;
  1807. }
  1808. savedMarketData["previousItemTimestamp"][dataBankItem.id] = Date.now();
  1809.  
  1810. //Used to check when to display tooltips
  1811. addPendingRequest();
  1812.  
  1813. var requestParams = {};
  1814. requestParams["tradezone"] = userData["tradezone"];
  1815. requestParams["searchname"] = encodeURI(dataBankItem.name.substring(0, 15));
  1816. requestParams["category"] = '';
  1817. requestParams["profession"] = '';
  1818. requestParams["memID"] = '';
  1819. requestParams["searchtype"] = "buyinglistitemname";
  1820. requestParams["search"] = "trades";
  1821.  
  1822. let requestCallback = function(responseText) {
  1823. dataBankItem.rawServerResponse = responseText;
  1824. filterItemResponseText(dataBankItem);
  1825. updateInventoryItemPrices(dataBankItem);
  1826. fillHoverBox();
  1827. completePendingRequest();
  1828. //Request cooked item counterpart if needed
  1829. if (itemsDataBank[dataBankItem.id + "_cooked"] != null) {
  1830. requestItem(itemsDataBank[dataBankItem.id + "_cooked"]);
  1831. }
  1832. return true;
  1833. };
  1834.  
  1835. return makeRequest("https://fairview.deadfrontier.com/onlinezombiemmo/trade_search.php", requestParams, requestCallback, null);
  1836.  
  1837. }
  1838. unsafeWindow.silverRequestItem = requestItem;
  1839.  
  1840. function filterItemResponseText(dataBankItem) {
  1841. var itemRawResponse = dataBankItem.rawServerResponse;
  1842. if (itemRawResponse != "") {
  1843. var maxTrades = [...itemRawResponse.matchAll(new RegExp("tradelist_[0-9]+_id_member=", "g"))].length;
  1844. var firstOccurence;
  1845. //Reset the trade list
  1846. if (itemRawResponse.indexOf("tradelist_maxresults=0") == -1) {
  1847. if (dataBankItem.extraInfo != "") {
  1848. firstOccurence = parseInt(itemRawResponse.match(new RegExp("tradelist_[0-9]+_item=" + dataBankItem.id))[0].split("=")[0].match(/[0-9]+/)[0]);
  1849. } else {
  1850. firstOccurence = parseInt(itemRawResponse.match(new RegExp("tradelist_[0-9]+_item=" + dataBankItem.id + "&"))[0].split("=")[0].match(/[0-9]+/)[0]);
  1851. }
  1852. } else {
  1853. firstOccurence = 0;
  1854. }
  1855. var availableTrades = maxTrades - firstOccurence;
  1856. var avgPrice = 0;
  1857. var examinedTrades = 0;
  1858.  
  1859. itemsDataBank[dataBankItem.id]["trades"] = [];
  1860. for (;
  1861. (examinedTrades < availableTrades) && (examinedTrades < 10); examinedTrades++) {
  1862. var pricePerUnit;
  1863. var quantity;
  1864. if (dataBankItem.type == "Armour") {
  1865. pricePerUnit = parseInt(itemRawResponse.match(new RegExp("tradelist_" + (firstOccurence + examinedTrades) + "_price=[0-9]+&"))[0].split("=")[1].match(/[0-9]+/)[0]);
  1866. } else {
  1867. //Fix for implants that are somehow listed in the market as having 0 quantity
  1868. quantity = parseInt(itemRawResponse.match(new RegExp("tradelist_" + (firstOccurence + examinedTrades) + "_quantity=[0-9]+&"))[0].split("=")[1].match(/[0-9]+/)[0]);
  1869. quantity = quantity < 1 ? 1 : quantity;
  1870. pricePerUnit = parseInt(itemRawResponse.match(new RegExp("tradelist_" + (firstOccurence + examinedTrades) + "_price=[0-9]+&"))[0].split("=")[1].match(/[0-9]+/)[0]) /
  1871. quantity;
  1872. }
  1873. avgPrice += pricePerUnit;
  1874. if (examinedTrades == 0) {
  1875. dataBankItem.bestPricePerUnit = pricePerUnit;
  1876. }
  1877.  
  1878. //Store the trade info for a future buyItem
  1879. var trade = {};
  1880. trade["tradeID"] = parseInt(itemRawResponse.match(new RegExp("tradelist_" + (firstOccurence + examinedTrades) + "_trade_id=[0-9]+&"))[0].split("=")[1].match(/[0-9]+/)[0]);
  1881. trade["price"] = parseInt(itemRawResponse.match(new RegExp("tradelist_" + (firstOccurence + examinedTrades) + "_price=[0-9]+&"))[0].split("=")[1].match(/[0-9]+/)[0]);
  1882. itemsDataBank[dataBankItem.id]["trades"].push(trade);
  1883. }
  1884.  
  1885. if (examinedTrades == 0) {
  1886. dataBankItem.averagePricePerUnit = 0;
  1887. dataBankItem.bestPricePerUnit = 0;
  1888. } else {
  1889. dataBankItem.averagePricePerUnit = avgPrice / examinedTrades;
  1890. }
  1891. //Fix undefined data
  1892. if (dataBankItem.averagePricePerUnit == undefined) {
  1893. dataBankItem.averagePricePerUnit = 0;
  1894. }
  1895. if (dataBankItem.bestPricePerUnit == undefined) {
  1896. dataBankItem.bestPricePerUnit = 0;
  1897. }
  1898. }
  1899. }
  1900.  
  1901. function updateInventoryItemPrices(dataBankItem) {
  1902. for (var x of inventoryArray) {
  1903. if (x.id == dataBankItem.id) {
  1904. x.bestPricePerUnit = dataBankItem.bestPricePerUnit;
  1905. x.averagePricePerUnit = dataBankItem.averagePricePerUnit;
  1906. }
  1907. }
  1908. }
  1909.  
  1910. //Buy an item that was previously registered into the databank
  1911. function buyItem(itemId) {
  1912. //Check that the databank has data on the item
  1913. if (itemsDataBank[itemId] == null || itemsDataBank[itemId]["trades"].length == 0) {
  1914. return false;
  1915. }
  1916.  
  1917. //Get the listing info
  1918. var itemBuynum = itemsDataBank[itemId]["trades"][0]["tradeID"];
  1919. var itemPrice = itemsDataBank[itemId]["trades"][0]["price"];
  1920.  
  1921. //Check that the item is tradeable/seeded into the market
  1922. if (itemBuynum == null) {
  1923. return false;
  1924. }
  1925.  
  1926. var requestParams = {};
  1927. requestParams["searchtype"] = "buyinglistitemname";
  1928. requestParams["creditsnum"] = "undefined";
  1929. requestParams["buynum"] = itemBuynum;
  1930. requestParams["renameto"] = "undefined`undefined";
  1931. requestParams["expected_itemprice"] = itemPrice;
  1932. requestParams["expected_itemtype2"] = "";
  1933. requestParams["expected_itemtype"] = "";
  1934. requestParams["itemnum2"] = "0";
  1935. requestParams["itemnum"] = "0";
  1936. requestParams["price"] = "0";
  1937. requestParams["action"] = "newbuy";
  1938.  
  1939. let requestCallback = function(responseText) {
  1940. //Check that the request didn't fail. If it did, discard the first service in the list as it became stale and retry.
  1941. if (responseText.length < 32) {
  1942. itemsDataBank[itemId]["trades"].shift();
  1943. if (itemsDataBank[itemId]["trades"].length > 0) {
  1944. buyService(itemId);
  1945. } else {
  1946. return false;
  1947. }
  1948. } else {
  1949. unsafeWindow.playSound("buysell");
  1950. //Update the inventory from the new data, according to the original source code
  1951. unsafeWindow.updateIntoArr(unsafeWindow.flshToArr(responseText, "DFSTATS_"), unsafeWindow.userVars);
  1952. unsafeWindow.populateInventory();
  1953. unsafeWindow.populateCharacterInventory();
  1954. unsafeWindow.updateAllFields();
  1955. initInventoryArray();
  1956. }
  1957. };
  1958.  
  1959. return makeRequest("https://fairview.deadfrontier.com/onlinezombiemmo/inventory_new.php", requestParams, requestCallback, null);
  1960. }
  1961.  
  1962.  
  1963. /////////////////////////////////
  1964. // Service Price Functions //
  1965. /////////////////////////////////
  1966.  
  1967. //Delete and request new service price info
  1968. function refreshServicesDataBank() {
  1969. if (!isAtLocation("inventories")) {
  1970. return;
  1971. }
  1972. //Check that we aren't exceeding the rate limit
  1973. if (Date.now() < savedMarketData["previousServicesTimestamp"] + 30000) {
  1974. return;
  1975. }
  1976. savedMarketData["previousServicesTimestamp"] = Date.now();
  1977. servicesDataBank = {
  1978. "Chef": {
  1979. name: "Chef"
  1980. },
  1981. "Doctor": {
  1982. name: "Doctor"
  1983. },
  1984. "Engineer": {
  1985. name: "Engineer"
  1986. }
  1987. };
  1988. requestServicesMarketInfo();
  1989. }
  1990.  
  1991. //Request info for every service in servicesDataBank
  1992. function requestServicesMarketInfo() {
  1993. for (var serviceName in servicesDataBank) {
  1994. addPendingRequest();
  1995. requestService(servicesDataBank[serviceName]);
  1996. }
  1997. }
  1998.  
  1999. function requestService(dataBankService) {
  2000. var requestParams = {};
  2001. requestParams["tradezone"] = userData["tradezone"];
  2002. requestParams["searchname"] = "";
  2003. requestParams["category"] = "";
  2004. requestParams["profession"] = encodeURI(dataBankService.name.substring(0, 15));
  2005. requestParams["memID"] = "";
  2006. requestParams["searchtype"] = "buyinglist";
  2007. requestParams["search"] = "services";
  2008.  
  2009. let requestCallback = function(responseText) {
  2010. dataBankService.rawServerResponse = responseText;
  2011. filterServiceResponseText(dataBankService);
  2012. completePendingRequest();
  2013. };
  2014.  
  2015. return makeRequest("https://fairview.deadfrontier.com/onlinezombiemmo/trade_search.php", requestParams, requestCallback, null);
  2016. }
  2017.  
  2018. function filterServiceResponseText(dataBankService) {
  2019. //Get length of response list
  2020. var rawServerResponse = dataBankService.rawServerResponse;
  2021. var responseLength = [...rawServerResponse.matchAll(new RegExp("tradelist_[0-9]+_id_member=", "g"))].length;
  2022. if (rawServerResponse != "") {
  2023. for (var i = 0; i < responseLength; i++) {
  2024. //If we don't already have price for this level, fetch the lowest
  2025. var serviceLevel = parseInt(rawServerResponse.match(new RegExp("tradelist_" + i + "_level=[0-9]+&"))[0].split("=")[1].match(/[0-9]+/)[0]);
  2026. if (dataBankService[serviceLevel] == undefined) {
  2027. dataBankService[serviceLevel] = [];
  2028. }
  2029. var service = {};
  2030. service["userID"] = parseInt(rawServerResponse.match(new RegExp("tradelist_" + i + "_id_member=[0-9]+&"))[0].split("=")[1].match(/[0-9]+/)[0]);
  2031. service["price"] = parseInt(rawServerResponse.match(new RegExp("tradelist_" + i + "_price=[0-9]+&"))[0].split("=")[1].match(/[0-9]+/)[0]);
  2032. dataBankService[serviceLevel].push(service);
  2033. }
  2034. }
  2035. }
  2036.  
  2037. //Buy service for a specified item
  2038. function buyService(slotNumber) {
  2039. var targetInventoryItem = inventoryArray[slotNumber - 1];
  2040. var serviceBuynum, servicePrice;
  2041.  
  2042. //We make sure that the item has an associated service
  2043. if (targetInventoryItem == '' || targetInventoryItem.profession == null) {
  2044. return false;
  2045. }
  2046.  
  2047. //Check that the databank has data on the service
  2048. if (servicesDataBank[targetInventoryItem.profession] == null ||
  2049. servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel].length == 0
  2050. ) {
  2051. return false;
  2052. }
  2053.  
  2054. //Get the listing info and lock the page until request is finished
  2055. serviceBuynum = servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel][0]["userID"];
  2056. servicePrice = servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel][0]["price"];
  2057. unsafeWindow.pageLock = true;
  2058.  
  2059. var requestParams = {};
  2060. requestParams["creditsnum"] = "0";
  2061. requestParams["buynum"] = serviceBuynum;
  2062. requestParams["renameto"] = "undefined`undefined";
  2063. requestParams["expected_itemprice"] = servicePrice;
  2064. requestParams["expected_itemtype2"] = "";
  2065. requestParams["expected_itemtype"] = "";
  2066. requestParams["itemnum2"] = "0";
  2067. requestParams["itemnum"] = slotNumber;
  2068. requestParams["price"] = "0";
  2069. requestParams["action"] = targetInventoryItem.serviceAction;
  2070.  
  2071. let requestCallback = function(responseText) {
  2072. //Unlock the page before doing anything else
  2073. unsafeWindow.pageLock = false;
  2074. //Check that the request didn't fail. If it did, discard the first service in the list as it became stale and retry.
  2075. if (responseText.length < 32) {
  2076. servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel].shift();
  2077. if (servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel].length > 0) {
  2078. buyService(slotNumber);
  2079. } else {
  2080. requestServicesMarketInfo();
  2081. }
  2082. } else {
  2083. unsafeWindow.playSound(targetInventoryItem.serviceSound);
  2084. //Update the inventory from the new data, according to the original source code
  2085. unsafeWindow.updateIntoArr(unsafeWindow.flshToArr(responseText, "DFSTATS_"), unsafeWindow.userVars);
  2086. unsafeWindow.populateInventory();
  2087. unsafeWindow.populateCharacterInventory();
  2088. unsafeWindow.updateAllFields();
  2089. return true;
  2090. }
  2091. };
  2092.  
  2093. return makeRequest("https://fairview.deadfrontier.com/onlinezombiemmo/inventory_new.php", requestParams, requestCallback, null);
  2094. }
  2095.  
  2096. function autoServiceHelper(targetInventoryItem, action) {
  2097. //Show custom box if a slot is hovered whilst the ALT is pressed
  2098. var mousePos = unsafeWindow.mousePos;
  2099. var playerCash = userVars["DFSTATS_df_cash"];
  2100. //Make sure the slot is occupied and not locked
  2101. if (targetInventoryItem.id != "" && unsafeWindow.lockedSlots.indexOf(lastSlotHovered) == -1) {
  2102. //Cookable OR damaged Armor OR Medical and health below max AND service is available
  2103. if ((
  2104. (targetInventoryItem.type == "Cookable") ||
  2105. (targetInventoryItem.type == "Armour" && targetInventoryItem.quantity < targetInventoryItem.maxHP) ||
  2106. (targetInventoryItem.type == "Medical" && parseInt(userVars["DFSTATS_df_hpcurrent"]) < parseInt(userVars["DFSTATS_df_hpmax"]))
  2107. ) && (servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel][0] != undefined)) {
  2108.  
  2109. var servicePrice = servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel][0]["price"];
  2110.  
  2111. if (servicePrice <= playerCash) {
  2112. if (action == "UpdateTooltip") {
  2113. unsafeWindow.tooltipDisplaying = true;
  2114. unsafeWindow.displayPlacementMessage(targetInventoryItem.serviceTooltip, mousePos[0] + 10, mousePos[1] + 10, "ACTION");
  2115. } else if (action == "BuyService") {
  2116. buyService(lastSlotHovered);
  2117. }
  2118. } else {
  2119. //If action is "BuyService" and the player doesn't have enough cash,
  2120. //don't fo anything
  2121. if (action == "UpdateTooltip") {
  2122. unsafeWindow.tooltipDisplaying = true;
  2123. unsafeWindow.displayPlacementMessage("You don't have enough cash to use this service!", mousePos[0] + 10, mousePos[1] + 10, "ERROR");
  2124. }
  2125. }
  2126. }
  2127. }
  2128. }
  2129.  
  2130. //////////////////////////
  2131. // Cash Functions //
  2132. /////////////////////////
  2133.  
  2134. function withdrawCash(amount) {
  2135. var requestParams = {};
  2136. requestParams["withdraw"] = amount;
  2137.  
  2138. let requestCallback = function(responseText) {
  2139. unsafeWindow.playSound("bank");
  2140. //We must filter out the new cash amounts and update the existing ones
  2141. var cashFields = responseText.split('&');
  2142. var newBankCash = cashFields[1].split('=')[1];
  2143. var newHeldCash = cashFields[2].split('=')[1];
  2144. userVars["DFSTATS_df_cash"] = newHeldCash;
  2145. userVars['DFSTATS_df_bankcash'] = newBankCash;
  2146. unsafeWindow.updateAllFields();
  2147. refreshMarketSearch();
  2148. };
  2149.  
  2150. return makeRequest("https://fairview.deadfrontier.com/onlinezombiemmo/bank.php", requestParams, requestCallback, null);
  2151. }
  2152.  
  2153. /////////////////////////////
  2154. // Loadout Functions //
  2155. ////////////////////////////
  2156.  
  2157. function openLoadoutsMenu() {
  2158. openHelpWindowPage("loadoutsMenu");
  2159. }
  2160.  
  2161. function loadoutOpenEquipMenu() {
  2162. for (var i = 0; i < 4; i++) {
  2163. helpWindowStructure["loadoutsListEquip"]["data"][i][3][1][1] = loadouts[i]["name"];
  2164. }
  2165. openHelpWindowPage("loadoutsListEquip");
  2166. }
  2167.  
  2168. function loadoutOpenEditMenu() {
  2169. for (var i = 0; i < 4; i++) {
  2170. helpWindowStructure["loadoutsListEdit"]["data"][i][3][1][1] = loadouts[i]["name"];
  2171. }
  2172. openHelpWindowPage("loadoutsListEdit");
  2173. }
  2174.  
  2175. function loadoutOpenResetMenu() {
  2176. for (var i = 0; i < 4; i++) {
  2177. helpWindowStructure["loadoutsListReset"]["data"][i][3][1][1] = loadouts[i]["name"];
  2178. }
  2179. openHelpWindowPage("loadoutsListReset");
  2180. }
  2181.  
  2182. function renameLoadout() {
  2183. var name = prompt("Choose name for this loadout");
  2184. if (name == null || name == "") {
  2185. name = "Default name";
  2186. } else {
  2187. name = name.substring(0, 15);
  2188. }
  2189. loadouts[activeLoadoutEdit]["name"] = name;
  2190. //Save loadout info
  2191. GM.setValue("loadouts", JSON.stringify(loadouts));
  2192. //Refresh edit page
  2193. editLoadout(activeLoadoutEdit);
  2194. }
  2195.  
  2196. function editLoadout(loadoutIndex) {
  2197. var loadout = loadouts[loadoutIndex];
  2198. activeLoadoutEdit = loadoutIndex;
  2199.  
  2200. //Set loadout title
  2201. helpWindowStructure["loadoutEdit"]["data"][1][3][1][1] = loadout["name"];
  2202. //Set armor
  2203. helpWindowStructure["loadoutEdit"]["data"][4][3][0][3][0][3] = []
  2204. if (loadout["armour"][0]["storageSlot"] != -1) {
  2205. var armorImg = ["div", "", {
  2206. "className": "item nonstack silverScriptsOccupiedSlot armour",
  2207. "dataset": [
  2208. ["type", loadout["armour"][0]["itemFlashType"]],
  2209. ["itemtype", loadout["armour"][0]["itemCategory"]],
  2210. ["quantity", loadout["armour"][0]["quantity"]]
  2211. ],
  2212. "style": [
  2213. ["backgroundImage", "url(\"https://files.deadfrontier.com/deadfrontier/inventoryimages/large/" + loadout["armour"][0]["itemFlashType"].split("_")[0] + ".png\")"]
  2214. ],
  2215. "id": "armour_0"
  2216. },
  2217. []
  2218. ];
  2219. helpWindowStructure["loadoutEdit"]["data"][4][3][0][3][0][3].push(armorImg);
  2220. }
  2221. //Set backpack
  2222. helpWindowStructure["loadoutEdit"]["data"][4][3][0][3][1][3] = []
  2223. if (loadout["backpack"][0]["storageSlot"] != -1) {
  2224. var armorImg = ["div", "", {
  2225. "className": "item nonstack silverScriptsOccupiedSlot backpack",
  2226. "dataset": [
  2227. ["type", loadout["backpack"][0]["itemFlashType"]],
  2228. ["itemtype", loadout["backpack"][0]["itemCategory"]]
  2229. ],
  2230. "style": [
  2231. ["backgroundImage", "url(\"https://files.deadfrontier.com/deadfrontier/inventoryimages/large/" + loadout["backpack"][0]["itemFlashType"].split("_")[0] + ".png\")"]
  2232. ],
  2233. "id": "backpack_0"
  2234. },
  2235. []
  2236. ];
  2237. helpWindowStructure["loadoutEdit"]["data"][4][3][0][3][1][3].push(armorImg);
  2238. }
  2239. //Set weapons
  2240. for (var i = 0; i < 3; i++) {
  2241. helpWindowStructure["loadoutEdit"]["data"][8][3][i][3] = []
  2242. if (loadout["weapon"][i]["storageSlot"] != -1) {
  2243. var weaponImg = ["div", "", {
  2244. "className": "item nonstack silverScriptsOccupiedSlot weapon",
  2245. "dataset": [
  2246. ["type", loadout["weapon"][i]["itemFlashType"]],
  2247. ["itemtype", loadout["weapon"][i]["itemCategory"]]
  2248. ],
  2249. "style": [
  2250. ["backgroundImage", "url(\"https://files.deadfrontier.com/deadfrontier/inventoryimages/large/" + loadout["weapon"][i]["itemFlashType"].split("_")[0] + ".png\")"]
  2251. ],
  2252. "id": "weapon_" + i
  2253. },
  2254. []
  2255. ];
  2256. helpWindowStructure["loadoutEdit"]["data"][8][3][i][3].push(weaponImg);
  2257. }
  2258. }
  2259. //Set implants
  2260. for (i = 0; i < 20; i++) {
  2261. //We have to handle the different rows
  2262. var implantRow = Math.floor(i / 4);
  2263. var implantColumn = i % 4;
  2264. //Hide the slots that are not available to the user
  2265. if (i >= userVars["DFSTATS_df_implantslots"]) {
  2266. helpWindowStructure["loadoutEdit"]["data"][12][3][implantRow][3][implantColumn][2]["style"].push(["display", "none"]);
  2267. continue;
  2268. }
  2269. helpWindowStructure["loadoutEdit"]["data"][12][3][implantRow][3][implantColumn][3] = [];
  2270. if (loadout["implant"][i]["storageSlot"] != -1) {
  2271. var implantImg = ["div", "", {
  2272. "className": "item nonstack silverScriptsOccupiedSlot implant",
  2273. "dataset": [
  2274. ["type", loadout["implant"][i]["itemFlashType"]],
  2275. ["itemtype", loadout["implant"][i]["itemCategory"]]
  2276. ],
  2277. "style": [
  2278. ["backgroundImage", "url(\"https://files.deadfrontier.com/deadfrontier/inventoryimages/large/" + loadout["implant"][i]["itemFlashType"].split("_")[0] + ".png\")"]
  2279. ],
  2280. "id": "implant_" + i
  2281. },
  2282. []
  2283. ];
  2284. helpWindowStructure["loadoutEdit"]["data"][12][3][implantRow][3][implantColumn][3].push(implantImg);
  2285. }
  2286. }
  2287.  
  2288. //Open edit page
  2289. openHelpWindowPage("loadoutEdit");
  2290.  
  2291. //We have to make the user select an item on click
  2292. var silverSlots = document.getElementsByClassName("silverScriptsSlot");
  2293. for (var slot of silverSlots) {
  2294. slot.addEventListener("click", pickLoadoutItem);
  2295. }
  2296.  
  2297. //We have to remove the pageLock if the mouse enters our slots
  2298. var occupiedSlots = document.getElementsByClassName("silverScriptsOccupiedSlot");
  2299. for (var occupiedSlot of occupiedSlots) {
  2300. occupiedSlot.addEventListener("mouseenter", function() {
  2301. unsafeWindow.pageLock = false;
  2302. });
  2303. occupiedSlot.addEventListener("mouseleave", function() {
  2304. unsafeWindow.pageLock = true;
  2305. });
  2306. }
  2307. }
  2308.  
  2309. function pickLoadoutItem(e) {
  2310. //Set category
  2311. var itemCategory = e.target.classList[e.target.classList.length - 1];
  2312. pickingLoadoutCategory = itemCategory;
  2313. //Set loadout index in category
  2314. pickingLoadoutSlotIndex = e.target.id.split("_")[1];
  2315.  
  2316. //Obscure items of the wrong category
  2317. filterStorageCategoriesDuringPicking();
  2318.  
  2319. closeHelpWindowPage();
  2320.  
  2321. //Spawn warning text
  2322. var warningText = document.createElement("p");
  2323. warningText.id = "silverScriptsWarning";
  2324. warningText.innerText = "Choose desired " + capitalizeFirstLetter(itemCategory);
  2325. warningText.style.color = "red";
  2326. warningText.style.position = "absolute";
  2327. warningText.style.fontSize = "30px";
  2328. warningText.style.top = "20px";
  2329. warningText.style.left = "160px";
  2330. warningText.style.zIndex = 20;
  2331. document.getElementById("inventoryholder").appendChild(warningText);
  2332.  
  2333. //Spawn back button
  2334. var backButton = document.createElement("button");
  2335. backButton.id = "silverScriptsBackButton";
  2336. backButton.innerText = "Back";
  2337. backButton.style.fontSize = "24px";
  2338. backButton.style.position = "absolute";
  2339. backButton.style.top = "450px";
  2340. backButton.style.left = "400px";
  2341. backButton.style.zIndex = 20;
  2342. backButton.addEventListener("click", loadoutCloseItemSelection);
  2343. document.getElementById("inventoryholder").appendChild(backButton);
  2344.  
  2345. //Spawn slot clear button
  2346. var slotClearSpan = document.createElement("span");
  2347. slotClearSpan.id = "silverScriptsClearSpan";
  2348. slotClearSpan.style.position = "absolute";
  2349. slotClearSpan.style.top = "450px";
  2350. slotClearSpan.style.left = "200px";
  2351. slotClearSpan.style.zIndex = 20;
  2352. slotClearSpan.dataset.slot = -1;
  2353. var clearButton = document.createElement("button");
  2354. clearButton.innerText = "Clear slot";
  2355. clearButton.style.fontSize = "24px";
  2356. clearButton.dataset.type = "";
  2357. clearButton.dataset.itemtype = "";
  2358. clearButton.dataset.quantity = -1;
  2359. clearButton.addEventListener("click", registerItemToLoadout);
  2360. slotClearSpan.appendChild(clearButton);
  2361. document.getElementById("inventoryholder").appendChild(slotClearSpan);
  2362. }
  2363.  
  2364. function filterStorageCategoriesDuringPicking() {
  2365. var storageContainer = document.getElementById("normalContainer");
  2366.  
  2367. //Block everything that isn't the storage
  2368. var pageCensor = document.createElement("div");
  2369. pageCensor.className = "silverScriptsCensor";
  2370. pageCensor.style.position = "absolute";
  2371. pageCensor.style.height = "100%";
  2372. pageCensor.style.width = "100%";
  2373. pageCensor.style.backgroundColor = "black";
  2374. pageCensor.style.zIndex = 10;
  2375. document.getElementById("inventoryholder").appendChild(pageCensor);
  2376. document.getElementById("invController").style.zIndex = 20;
  2377. document.getElementById("storageForward").style.zIndex = 20;
  2378. document.getElementById("storageBackward").style.zIndex = 20;
  2379.  
  2380. var inventoryCensor = document.createElement("div");
  2381. inventoryCensor.className = "silverScriptsCensor";
  2382. inventoryCensor.style.position = "absolute";
  2383. inventoryCensor.style.height = "100%";
  2384. inventoryCensor.style.width = "100%";
  2385. inventoryCensor.style.top = "0px";
  2386. inventoryCensor.style.left = "0px";
  2387. inventoryCensor.style.backgroundColor = "black";
  2388. inventoryCensor.style.zIndex = 10;
  2389. document.getElementById("inventory").appendChild(inventoryCensor);
  2390.  
  2391. for (var storageSlot of storageContainer.childNodes) {
  2392. filterStorageSlotDuringPicking(storageSlot);
  2393. }
  2394. }
  2395.  
  2396. function filterStorageSlotDuringPicking(storageSlot) {
  2397. var censor = document.createElement("div");
  2398. censor.className = "silverScriptsCensor";
  2399. censor.style.height = "40px";
  2400. censor.style.width = "40px";
  2401. censor.style.backgroundColor = "black";
  2402. censor.style.opacity = "0.8";
  2403. censor.style.zIndex = 5;
  2404.  
  2405. if (storageSlot.childNodes.length == 0) {
  2406. storageSlot.appendChild(censor);
  2407. } else {
  2408. var itemFlashType = storageSlot.childNodes[0].dataset.type;
  2409. var itemGlobData = globalData[itemFlashType.split("_")[0]];
  2410. var itemCategory = storageSlot.childNodes[0].dataset.itemtype;
  2411.  
  2412. if (itemCategory != pickingLoadoutCategory || //Filter out items of a different category from the one that is being selected
  2413. (itemCategory == "implant" && !loadoutCheckValidImplantPick(itemFlashType)) || //Filter out invalid implant combinations
  2414. (itemCategory == "weapon" && itemGlobData["str_req"] != undefined && itemGlobData["str_req"] > 0 && userVars["DFSTATS_df_strength"] < itemGlobData["str_req"]) || //Filter out weapons requiring more strength than the player has
  2415. (itemCategory == "weapon" && itemGlobData["pro_req"] != undefined && parseInt(itemGlobData["pro_req"]) > 0 && userVars["DFSTATS_df_pro" + itemGlobData["wepPro"]] < itemGlobData["pro_req"]) || //Filter out weapons requiring more proficiency than the player has
  2416. (itemCategory == "armour" && itemGlobData["str_req"] != undefined && itemGlobData["str_req"] > 0 && parseInt(userVars["DFSTATS_df_strength"]) < parseInt(itemGlobData["str_req"])) || //Filter out armours requiring more strength than the player has
  2417. !loadoutCheckSlotUnused(storageSlot.dataset.slot) //Make the sure the same item cannot be equipped twice per loadout
  2418. ) {
  2419. storageSlot.childNodes[0].appendChild(censor);
  2420. } else {
  2421. storageSlot.childNodes[0].addEventListener("click", registerItemToLoadout);
  2422. }
  2423. }
  2424. }
  2425.  
  2426. function loadoutCheckValidImplantPick(itemFlashType) {
  2427. //Fetch info on the target implant
  2428. var restrictions = getImplantRestrictions(itemFlashType);
  2429. //Check if any implant in the loadout fails the checks
  2430. for (var i = 0; i < 16; i++) {
  2431. var loadoutImplant = loadouts[activeLoadoutEdit]["implant"][i];
  2432. //Only check occupied loadout slots
  2433. if (loadoutImplant["storageSlot"] == -1) {
  2434. continue;
  2435. }
  2436. if (restrictions["forbiddenImplants"].indexOf(loadoutImplant["itemFlashType"]) != -1) {
  2437. return false;
  2438. }
  2439. if (restrictions["isUnique"] && loadoutImplant["itemFlashType"] == itemFlashType) {
  2440. return false;
  2441. }
  2442. }
  2443. //If we get here, no checks failed
  2444. return true;
  2445. }
  2446.  
  2447. function loadoutCheckValidImplantEquip(itemFlashType, characterSlotNumber) {
  2448. //Fetch info on the target implant
  2449. var restrictions = getImplantRestrictions(itemFlashType);
  2450. //Check if restrictions still apply to currently equipped implants
  2451. var implantSlotsAmount = userVars["DFSTATS_df_implantslots"];
  2452. var targetImplantNumber = characterSlotNumber - 1000;
  2453. for (var i = 1; i <= implantSlotsAmount; i++) {
  2454. //Skip checking the destination slot
  2455. if (i == targetImplantNumber) {
  2456. continue;
  2457. }
  2458. var implantType = userVars["DFSTATS_df_implant" + i + "_type"];
  2459. if (restrictions["forbiddenImplants"].indexOf(implantType) != -1) {
  2460. return false;
  2461. }
  2462. if (restrictions["isUnique"] && implantType == itemFlashType) {
  2463. return false;
  2464. }
  2465. }
  2466. //If we get here, no checks failed
  2467. return true;
  2468. }
  2469.  
  2470. function loadoutCheckSlotUnused(storageSlot) {
  2471. //Check that the desired item was not previously selected for the loadout
  2472. var loadout = loadouts[activeLoadoutEdit];
  2473. if (loadout["armour"][0]["storageSlot"] == storageSlot) {
  2474. return false;
  2475. }
  2476. if (loadout["backpack"][0]["storageSlot"] == storageSlot) {
  2477. return false;
  2478. }
  2479. for (var i = 0; i < 3; i++) {
  2480. if (loadout["weapon"][i]["storageSlot"] == storageSlot) {
  2481. return false;
  2482. }
  2483. }
  2484. for (i = 0; i < 16; i++) {
  2485. if (loadout["implant"][i]["storageSlot"] == storageSlot) {
  2486. return false;
  2487. }
  2488. }
  2489. //If we get here, no checks failed
  2490. return true;
  2491. }
  2492.  
  2493. function loadoutCloseItemSelection() {
  2494. //Reset editing values
  2495. pickingLoadoutCategory = "";
  2496. pickingLoadoutSlotIndex = -1;
  2497.  
  2498. //Remove all the censors and the warning
  2499. var censors = document.getElementsByClassName("silverScriptsCensor");
  2500. for (var i = censors.length - 1; i >= 0; i--) {
  2501. censors[i].parentNode.removeChild(censors[i]);
  2502. }
  2503. document.getElementById("silverScriptsWarning").remove();
  2504. document.getElementById("silverScriptsBackButton").remove();
  2505. document.getElementById("silverScriptsClearSpan").remove();
  2506. document.getElementById("invController").style.zIndex = "";
  2507. document.getElementById("storageForward").style.zIndex = "";
  2508. document.getElementById("storageBackward").style.zIndex = "";
  2509.  
  2510. //Remove the click listeners
  2511. var storageSlots = document.getElementById("normalContainer").childNodes;
  2512. for (var storageSlot of storageSlots) {
  2513. if (storageSlot.childNodes.length > 0) {
  2514. storageSlot.childNodes[0].removeEventListener("click", registerItemToLoadout);
  2515. }
  2516. }
  2517.  
  2518. //Refresh the used item overlays
  2519. loadoutRefreshUsedSlotOverlay();
  2520.  
  2521. //Open back the edit menu window
  2522. editLoadout(activeLoadoutEdit);
  2523. }
  2524.  
  2525. function loadoutAddUsedSlotOverlay(elem) {
  2526. var storageSlotNumber = elem.dataset.slot;
  2527. var overlay = document.createElement("p");
  2528. overlay.className = "silverScriptsLoadoutIndicator";
  2529. overlay.style.position = "absolute";
  2530. overlay.style.margin = "0px";
  2531. overlay.style.bottom = "0px";
  2532. overlay.style.right = "0px";
  2533. overlay.style.pointerEvents = "none";
  2534.  
  2535. //Check if the supplied slot is used in any loadout
  2536. for (var i = 0; i < 4; i++) {
  2537. var found = false;
  2538. var progress = ((i + 1) * 64) - 1;
  2539. var r = progress;
  2540. var g = Math.floor(255 - (255 * (Math.abs(progress - 127) / 128)));
  2541. var b = 255 - progress;
  2542. for (var j = 0; j < 1 && !found; j++) {
  2543. if (loadouts[i]["armour"][j]["storageSlot"] == storageSlotNumber) {
  2544. found = true;
  2545. }
  2546. }
  2547. for (var j = 0; j < 1 && !found; j++) {
  2548. if (loadouts[i]["backpack"][j]["storageSlot"] == storageSlotNumber) {
  2549. found = true;
  2550. }
  2551. }
  2552. for (j = 0; j < 3 && !found; j++) {
  2553. if (loadouts[i]["weapon"][j]["storageSlot"] == storageSlotNumber) {
  2554. found = true;
  2555. }
  2556. }
  2557. for (j = 0; j < 16 && !found; j++) {
  2558. if (loadouts[i]["implant"][j]["storageSlot"] == storageSlotNumber) {
  2559. found = true;
  2560. }
  2561. }
  2562. if (found) {
  2563. var numberIndicator = document.createElement("span");
  2564. numberIndicator.innerText = (i + 1) + " ";
  2565. //numberIndicator.style.color = "red"
  2566. numberIndicator.style.color = "rgb(" + r + "," + g + "," + b + ")";
  2567. overlay.appendChild(numberIndicator);
  2568. }
  2569. }
  2570. if (overlay.childNodes.length > 0) {
  2571. elem.childNodes[0].appendChild(overlay);
  2572. }
  2573. }
  2574.  
  2575. function loadoutRefreshUsedSlotOverlay() {
  2576. //Don't do anything if the Storage isn't open
  2577. if (!isAtLocation("storage")) {
  2578. return;
  2579. }
  2580. //First of all remove all the old overlays if any are present
  2581. var overlays = document.getElementsByClassName("silverScriptsLoadoutIndicator");
  2582. for (var i = overlays.length - 1; i >= 0; i--) {
  2583. overlays[i].parentNode.removeChild(overlays[i]);
  2584. }
  2585. //Refresh all visible storage slots
  2586. var slots = document.getElementById("normalContainer").childNodes;
  2587. for (var slot of slots) {
  2588. loadoutAddUsedSlotOverlay(slot);
  2589. }
  2590. }
  2591.  
  2592. function registerItemToLoadout(e) {
  2593.  
  2594. //Update loadout slot info
  2595. var newItemData = e.target.dataset;
  2596.  
  2597. //Clean implant data from extra data if needed
  2598. if (pickingLoadoutCategory == "implant" && newItemData.itemtype != '') {
  2599. newItemData.itemtype = newItemData.itemtype.split("_")[0];
  2600. }
  2601.  
  2602. loadouts[activeLoadoutEdit][pickingLoadoutCategory][pickingLoadoutSlotIndex]["storageSlot"] = e.target.parentNode.dataset.slot;
  2603. loadouts[activeLoadoutEdit][pickingLoadoutCategory][pickingLoadoutSlotIndex]["itemFlashType"] = newItemData.type;
  2604. loadouts[activeLoadoutEdit][pickingLoadoutCategory][pickingLoadoutSlotIndex]["itemCategory"] = newItemData.itemtype;
  2605. loadouts[activeLoadoutEdit][pickingLoadoutCategory][pickingLoadoutSlotIndex]["quantity"] = newItemData.quantity;
  2606.  
  2607. //Save loadout info
  2608. GM.setValue("loadouts", JSON.stringify(loadouts));
  2609.  
  2610. //Go back to editing
  2611. loadoutCloseItemSelection();
  2612. }
  2613.  
  2614. function resetLoadout(loadoutIndex) {
  2615. var loadoutBlueprint = {
  2616. "name": "Default Name",
  2617. "armour": [{
  2618. "characterSlotType": "DFSTATS_df_armourtype",
  2619. "characterSlotNumber": 34,
  2620. "storageSlot": -1,
  2621. "itemFlashType": "",
  2622. "itemCategory": ""
  2623. }],
  2624. "backpack": [{
  2625. "characterSlotType": "DFSTATS_df_backpack",
  2626. "characterSlotNumber": 35,
  2627. "storageSlot": -1,
  2628. "itemFlashType": "",
  2629. "itemCategory": ""
  2630. }],
  2631. "weapon": [{
  2632. "characterSlotType": "DFSTATS_df_weapon1type",
  2633. "characterSlotNumber": 31,
  2634. "storageSlot": -1,
  2635. "itemFlashType": "",
  2636. "itemCategory": ""
  2637. },
  2638. {
  2639. "characterSlotType": "DFSTATS_df_weapon2type",
  2640. "characterSlotNumber": 32,
  2641. "storageSlot": -1,
  2642. "itemFlashType": "",
  2643. "itemCategory": ""
  2644. },
  2645. {
  2646. "characterSlotType": "DFSTATS_df_weapon3type",
  2647. "characterSlotNumber": 33,
  2648. "storageSlot": -1,
  2649. "itemFlashType": "",
  2650. "itemCategory": ""
  2651. },
  2652. ],
  2653. "hat": [{
  2654. "characterSlotType": "DFSTATS_df_avatar_hat",
  2655. "characterSlotNumber": 40,
  2656. "storageSlot": -1,
  2657. "itemFlashType": "",
  2658. "itemCategory": ""
  2659. }],
  2660. "mask": [{
  2661. "characterSlotType": "DFSTATS_df_avatar_mask",
  2662. "characterSlotNumber": 39,
  2663. "storageSlot": -1,
  2664. "itemFlashType": "",
  2665. "itemCategory": ""
  2666. }],
  2667. "coat": [{
  2668. "characterSlotType": "DFSTATS_df_avatar_coat",
  2669. "characterSlotNumber": 38,
  2670. "storageSlot": -1,
  2671. "itemFlashType": "",
  2672. "itemCategory": ""
  2673. }],
  2674. "shirt": [{
  2675. "characterSlotType": "DFSTATS_df_avatar_shirt",
  2676. "characterSlotNumber": 36,
  2677. "storageSlot": -1,
  2678. "itemFlashType": "",
  2679. "itemCategory": ""
  2680. }],
  2681. "trousers": [{
  2682. "characterSlotType": "DFSTATS_df_avatar_trousers",
  2683. "characterSlotNumber": 37,
  2684. "storageSlot": -1,
  2685. "itemFlashType": "",
  2686. "itemCategory": ""
  2687. }],
  2688. "implant": [],
  2689. };
  2690.  
  2691. for (var i = 0; i < 20; i++) {
  2692. loadoutBlueprint["implant"].push({
  2693. "characterSlotType": "",
  2694. "characterSlotNumber": 1001 + i,
  2695. "storageSlot": -1,
  2696. "itemFlashType": "",
  2697. "itemCategory": ""
  2698. });
  2699. }
  2700.  
  2701. loadouts[loadoutIndex] = loadoutBlueprint;
  2702. }
  2703.  
  2704. function resetLoadoutButton(loadoutIndex) {
  2705. resetLoadout(loadoutIndex);
  2706. //Save loadout
  2707. GM.setValue("loadouts", JSON.stringify(loadouts));
  2708. loadoutRefreshUsedSlotOverlay();
  2709. loadoutOpenResetMenu();
  2710. }
  2711.  
  2712. function equipCurrentLoadout() {
  2713. equipLoadout(activeLoadoutEdit);
  2714. }
  2715.  
  2716. function equipLoadout(loadoutNumber) {
  2717. //Make sure at least an inventory slot is empty
  2718. if (unsafeWindow.findFirstEmptyGenericSlot("inv") === false) {
  2719. alert("You need at least 1 free inventory slot to equip a QuickSwap");
  2720. return;
  2721. }
  2722. //We have to prepare the swap chains. Go through each item in a loadout.
  2723. var swapList = [];
  2724. for (var category in loadouts[loadoutNumber]) {
  2725. if (category == "name") {
  2726. continue;
  2727. }
  2728. for (var i = 0; i < loadouts[loadoutNumber][category].length; i++) {
  2729. var loadoutItem = loadouts[loadoutNumber][category][i];
  2730. if (loadoutItem["storageSlot"] != -1) {
  2731. var itemChainData = {};
  2732. itemChainData["loadout"] = loadoutNumber;
  2733. itemChainData["category"] = category;
  2734. itemChainData["itemIndex"] = i;
  2735. var isImplant = category == "implant";
  2736. //Check that if the user is trying to equip an implant, its restrictions still apply
  2737. if (isImplant && !loadoutCheckValidImplantEquip(loadoutItem["itemFlashType"], loadoutItem["characterSlotNumber"])) {
  2738. alert("The following implant isn't following restrictions: " + globalData[loadoutItem["itemFlashType"]]["name"] + "\nMake sure you are not trying to equip a Unique implant while already having one equipped\nIf you weere trying to equip an implant with restrictions, make sure to use the same slot in the Quickswap as the one where the blocked implant is currently equipped in your Inventory");
  2739. return;
  2740. }
  2741. itemChainData["chainData"] = loadoutMakeEquipSlotChain(loadoutItem["storageSlot"], loadoutItem["characterSlotNumber"], loadoutItem["characterSlotType"], isImplant);
  2742. swapList.push(itemChainData);
  2743. }
  2744. }
  2745. }
  2746. //Start the swap chains once every item in the loadout has been scanned
  2747. if (swapList.length > 0) {
  2748. closeHelpWindowPage();
  2749. unsafeWindow.pageLock = true;
  2750. helpWindow.innerHTML = "<div style='text-align: center'>Loading, please wait...</div>";
  2751. helpWindow.parentNode.style.display = "block";
  2752. loadoutEquipChainStart(swapList);
  2753. }
  2754. }
  2755.  
  2756. function loadoutMakeEquipSlotChain(storageSlotNumber, equipSlotNumber, equipSlotType, isImplant) {
  2757. var invFreeSlotNumber = unsafeWindow.findFirstEmptyGenericSlot("inv");
  2758. if (invFreeSlotNumber === false) {
  2759. return -1;
  2760. }
  2761. var chainParams = {}
  2762. var takeItemFlashName = unsafeWindow.storageBox["df_store" + storageSlotNumber + "_type"];
  2763. var takeItemFlashData = globalData[takeItemFlashName.split("_")[0]];
  2764. if (takeItemFlashData["itemcat"] != "weapon" && takeItemFlashData["itemcat"] != "armour" && takeItemFlashData["itemcat"] != "backpack" && takeItemFlashData["implant"] != "1") {
  2765. return -2;
  2766. }
  2767. //Craft the shared params and clone them to all the requests
  2768. chainParams["take"] = {}
  2769. chainParams["take"]["creditsnum"] = userVars["DFSTATS_df_credits"];
  2770. chainParams["take"]["buynum"] = "0";
  2771. chainParams["take"]["renameto"] = "undefined`undefined";
  2772. chainParams["take"]["expected_itemprice"] = "-1";
  2773. chainParams["take"]["price"] = unsafeWindow.getUpgradePrice();
  2774.  
  2775. chainParams["equip"] = cloneObject(chainParams["take"]);
  2776. chainParams["store"] = cloneObject(chainParams["take"]);
  2777.  
  2778. chainParams["take"]["expected_itemtype2"] = "";
  2779. chainParams["take"]["expected_itemtype"] = takeItemFlashName;
  2780. chainParams["take"]["itemnum2"] = invFreeSlotNumber;
  2781. chainParams["take"]["itemnum"] = 40 + parseInt(storageSlotNumber);
  2782. chainParams["take"]["action"] = "take";
  2783.  
  2784. var oldEquipItemType = '';
  2785. if (isImplant) {
  2786. oldEquipItemType = userVars["DFSTATS_df_implant" + (equipSlotNumber - 1000) + "_type"];
  2787. } else {
  2788. oldEquipItemType = userVars[equipSlotType];
  2789. }
  2790. chainParams["equip"]["expected_itemtype2"] = oldEquipItemType;
  2791. chainParams["equip"]["expected_itemtype"] = takeItemFlashName;
  2792. chainParams["equip"]["itemnum2"] = equipSlotNumber;
  2793. chainParams["equip"]["itemnum"] = invFreeSlotNumber;
  2794. if (isImplant) {
  2795. chainParams["equip"]["action"] = "newswap";
  2796. } else {
  2797. chainParams["equip"]["action"] = "newequip";
  2798. }
  2799.  
  2800. chainParams["store"]["expected_itemtype2"] = "";
  2801. chainParams["store"]["expected_itemtype"] = oldEquipItemType;
  2802. chainParams["store"]["itemnum2"] = 40 + parseInt(storageSlotNumber);
  2803. chainParams["store"]["itemnum"] = invFreeSlotNumber;
  2804. chainParams["store"]["action"] = "store";
  2805.  
  2806. return chainParams;
  2807. }
  2808.  
  2809. function loadoutEquipChainStart(swapList) {
  2810. if (swapList.length > 0) {
  2811. loadoutEquipChainTake(swapList);
  2812. }
  2813. }
  2814.  
  2815. function loadoutEquipChainTake(chainParams) {
  2816. makeRequest("https://fairview.deadfrontier.com/onlinezombiemmo/inventory_new.php", chainParams[0]["chainData"]["take"], loadoutEquipChainSwap, chainParams);
  2817. }
  2818.  
  2819. //DO NOT USE ON ITS OWN
  2820. function loadoutEquipChainSwap(inventoryData, chainParams) {
  2821. unsafeWindow.updateIntoArr(unsafeWindow.flshToArr(inventoryData, "DFSTATS_"), unsafeWindow.userVars);
  2822. makeRequest("https://fairview.deadfrontier.com/onlinezombiemmo/inventory_new.php", chainParams[0]["chainData"]["equip"], loadoutEquipChainStore, chainParams);
  2823. }
  2824.  
  2825. //DO NOT USE ON ITS OWN
  2826. function loadoutEquipChainStore(inventoryData, chainParams) {
  2827. unsafeWindow.updateIntoArr(unsafeWindow.flshToArr(inventoryData, "DFSTATS_"), unsafeWindow.userVars);
  2828. //Store info in the loadout regarding the new item that was taken off. It should have landed in our designated free inventory slot.
  2829. var loadoutData = chainParams[0];
  2830. for (var loadoutCategory in loadouts[0]) {
  2831. if (loadoutCategory == "name") {
  2832. continue;
  2833. }
  2834. for (var loadoutIndex = 0; loadoutIndex < loadouts.length; loadoutIndex++) {
  2835. for (var itemIndex = 0; itemIndex < loadouts[loadoutIndex][loadoutCategory].length; itemIndex++) {
  2836. if (loadouts[loadoutIndex][loadoutCategory][itemIndex]["storageSlot"] == (chainParams[0]["chainData"]["take"]["itemnum"] - 40)) {
  2837. loadouts[loadoutIndex][loadoutCategory][itemIndex]["itemFlashType"] = userVars["DFSTATS_df_inv" + chainParams[0]["chainData"]["equip"]["itemnum"] + "_type"];
  2838. }
  2839. }
  2840. }
  2841. }
  2842. makeRequest("https://fairview.deadfrontier.com/onlinezombiemmo/inventory_new.php", chainParams[0]["chainData"]["store"], loadoutEquipChainReload, chainParams);
  2843. }
  2844.  
  2845. //DO NOT USE ON ITS OWN
  2846. function loadoutEquipChainReload(storageData, chainParams) {
  2847. unsafeWindow.playSound("bank");
  2848. chainParams.shift();
  2849. //Check if all the chain items have been consumed
  2850. if (chainParams.length > 0) {
  2851. loadoutEquipChainStart(chainParams);
  2852. } else {
  2853. GM.setValue("loadouts", JSON.stringify(loadouts));
  2854. unsafeWindow.reloadStorageData(storageData);
  2855. unsafeWindow.reloadInventoryData();
  2856. }
  2857. }
  2858.  
  2859. function exportLoadoutsToJson() {
  2860. var loadoutsJson = JSON.stringify(loadouts);
  2861. var loadoutsFileName = "silverScriptsLoadoutsExport.json";
  2862. downloadJsonFile(loadoutsFileName, loadoutsJson);
  2863. }
  2864.  
  2865. //Code from https://stackoverflow.com/questions/3665115/how-to-create-a-file-in-memory-for-user-to-download-but-not-through-server
  2866. function downloadJsonFile(filename, data) {
  2867. const blob = new Blob([data], {
  2868. type: 'application/json;charset=utf-8;'
  2869. });
  2870. if (window.navigator.msSaveOrOpenBlob) {
  2871. window.navigator.msSaveBlob(blob, filename);
  2872. } else {
  2873. const elem = window.document.createElement('a');
  2874. elem.href = window.URL.createObjectURL(blob, {
  2875. oneTimeOnly: true
  2876. });
  2877. elem.download = filename;
  2878. document.body.appendChild(elem);
  2879. elem.click();
  2880. document.body.removeChild(elem);
  2881. }
  2882. }
  2883.  
  2884. async function importLoadoutsFromJson() {
  2885. //Wait for file
  2886. const loadoutsJson = await getJsonUpload();
  2887. //Load result as the loadouts
  2888. loadouts = JSON.parse(loadoutsJson);
  2889. //Save the new loadouts
  2890. GM.setValue("loadouts", JSON.stringify(loadouts));
  2891. //Reload the loadouts
  2892. initLoadouts();
  2893. //Close menu
  2894. closeHelpWindowPage();
  2895. }
  2896.  
  2897. //Code from https://stackoverflow.com/questions/36127648/uploading-a-json-file-and-using-it
  2898. //I am bad at Promises, this is a tweaked multi-file solution, forgive me
  2899. const getJsonUpload = () =>
  2900. new Promise(resolve => {
  2901. const inputFileElement = document.createElement('input')
  2902. inputFileElement.setAttribute('type', 'file')
  2903. inputFileElement.setAttribute('accept', '.json')
  2904.  
  2905. inputFileElement.addEventListener(
  2906. 'change',
  2907. async (event) => {
  2908. const {
  2909. files
  2910. } = event.target
  2911. if (!files) {
  2912. return
  2913. }
  2914.  
  2915. const filePromises = [...files].map(file => file.text())
  2916.  
  2917. resolve(await Promise.any(filePromises))
  2918. },
  2919. false,
  2920. )
  2921. inputFileElement.click()
  2922. })
  2923.  
  2924. ////////////////////////////
  2925. // Cosmetic Loadouts //
  2926. ////////////////////////////
  2927.  
  2928. function makeLoadoutCosmetic(loadoutNumber) {
  2929. if (typeof loadoutNumber != "number" ||
  2930. loadoutNumber < 0 ||
  2931. loadoutNumber > 3) {
  2932. loadoutNumber = activeLoadoutEdit;
  2933. }
  2934.  
  2935. let sidebarDiv = document.getElementsByClassName("characterRender")[0];
  2936. let updatedVars = unsafeWindow.userVars;
  2937. let loadout = loadouts[loadoutNumber];
  2938.  
  2939. for (let category of Object.values(loadout)) {
  2940. for (let item of category) {
  2941. if (item["characterSlotType"] != '' && item["storageSlot"] != -1) {
  2942. updatedVars[item["characterSlotType"]] = item["itemFlashType"]
  2943. }
  2944. }
  2945. }
  2946. unsafeWindow.renderAvatarUpdate(sidebarDiv, updatedVars);
  2947. }
  2948. unsafeWindow.makeLoadoutCosmetic = makeLoadoutCosmetic;
  2949.  
  2950. //////////////////////
  2951. // Boost Timer //
  2952. //////////////////////
  2953.  
  2954. function addBoostTimers() {
  2955. //The IC Inventory doesn't have boost texts, and we don't have access to player data in the forum
  2956. if (isAtLocation("ICInventory") || isAtLocation("forum")) {
  2957. return;
  2958. }
  2959. var boostersDiv = document.getElementsByClassName("boostTimes")[0];
  2960. var newBoostText = "";
  2961. var boostTexts = boostersDiv.innerHTML.split("<br>");
  2962. //Check which boosts are active
  2963. for (var i = 0; i < boostTexts.length; i++) {
  2964. if (boostTexts[i].indexOf("Exp") != -1) {
  2965. if (newBoostText != "") {
  2966. newBoostText += "<br />"
  2967. }
  2968. newBoostText += "+50% Exp Boost <span id='silverExpBoostDurationTimer'></span>";
  2969. continue;
  2970. }
  2971. if (boostTexts[i].indexOf("Damage") != -1) {
  2972. if (newBoostText != "") {
  2973. newBoostText += "<br />"
  2974. }
  2975. newBoostText += "+35% Damage Boost <span id='silverDmgBoostDurationTimer'></span>";
  2976. continue;
  2977. }
  2978. if (boostTexts[i].indexOf("Speed") != -1) {
  2979. if (newBoostText != "") {
  2980. newBoostText += "<br />"
  2981. }
  2982. newBoostText += "+35% Speed Boost <span id='silverSpdBoostDurationTimer'></span>";
  2983. continue;
  2984. }
  2985. }
  2986. if (newBoostText != "") {
  2987. boostersDiv.innerHTML = newBoostText;
  2988. startBoostTimersCountdown();
  2989. }
  2990. }
  2991.  
  2992. function startBoostTimersCountdown() {
  2993. setInterval(function() {
  2994. var durationLeft = 0;
  2995. //We have to manually keep track of the updated server time
  2996. userVars["DFSTATS_df_servertime"] = parseInt(userVars["DFSTATS_df_servertime"]) + 1;
  2997.  
  2998. var expSpan = document.getElementById("silverExpBoostDurationTimer");
  2999. if (expSpan != undefined && expSpan.innerHTML != "(∞)") {
  3000. durationLeft = parseInt(userVars["DFSTATS_df_boostexpuntil"]) - (parseInt(userVars["DFSTATS_df_servertime"]) + 1200000000);
  3001. if (durationLeft > 0) {
  3002. if (durationLeft > 600000) {
  3003. expSpan.innerHTML = "(∞)";
  3004. } else {
  3005. expSpan.innerHTML = `(${secondsToHms(durationLeft)})`;
  3006. }
  3007. }
  3008. }
  3009. var dmgSpan = document.getElementById("silverDmgBoostDurationTimer");
  3010. if (dmgSpan != undefined && dmgSpan.innerHTML != "(∞)") {
  3011. durationLeft = parseInt(userVars["DFSTATS_df_boostdamageuntil"]) - (parseInt(userVars["DFSTATS_df_servertime"]) + 1200000000);
  3012. if (durationLeft > 0) {
  3013. if (durationLeft > 600000) {
  3014. dmgSpan.innerHTML = "(∞)";
  3015. } else {
  3016. dmgSpan.innerHTML = `(${secondsToHms(durationLeft)})`;
  3017. }
  3018. }
  3019. }
  3020. var spdSpan = document.getElementById("silverSpdBoostDurationTimer");
  3021. if (spdSpan != undefined && spdSpan.innerHTML != "(∞)") {
  3022. durationLeft = parseInt(userVars["DFSTATS_df_boostexpuntil"]) - (parseInt(userVars["DFSTATS_df_servertime"]) + 1200000000);
  3023. if (durationLeft > 0) {
  3024. if (durationLeft > 600000) {
  3025. spdSpan.innerHTML = "(∞)";
  3026. } else {
  3027. spdSpan.innerHTML = `(${secondsToHms(durationLeft)})`;
  3028. }
  3029. }
  3030. }
  3031. }, 1000);
  3032. }
  3033.  
  3034. /////////////////////////
  3035. // Quick Service //
  3036. ////////////////////////
  3037.  
  3038. function quickServiceArmorHelper(action) {
  3039. //Show custom box the icon is hovered whilst the ALT is pressed
  3040. var mousePos = unsafeWindow.mousePos;
  3041. var playerCash = userVars["DFSTATS_df_cash"];
  3042. var armorType = userVars['DFSTATS_df_armourtype'].split("_")[0];
  3043. //Make sure an armor is equipped and damaged
  3044. if (armorType != '' && parseInt(userVars['DFSTATS_df_armourhp']) < parseInt(userVars['DFSTATS_df_armourhpmax'])) {
  3045. var armorData = globalData[armorType];
  3046. var repairLevel = parseInt(armorData.shop_level) - 5;
  3047. //Check that at least 1 service is available
  3048. if (servicesDataBank["Engineer"][repairLevel][0] != undefined) {
  3049.  
  3050. var servicePrice = servicesDataBank["Engineer"][repairLevel][0]["price"];
  3051. var slotNumber = unsafeWindow.findFirstEmptyGenericSlot("inv");
  3052.  
  3053. if (servicePrice <= playerCash) {
  3054. if (slotNumber != false) {
  3055. if (action == "UpdateTooltip") {
  3056. unsafeWindow.tooltipDisplaying = true;
  3057. unsafeWindow.displayPlacementMessage("Repair", mousePos[0] + 10, mousePos[1] + 10, "ACTION");
  3058. } else if (action == "BuyService") {
  3059. quickServiceArmorBuyService();
  3060. }
  3061. } else {
  3062. unsafeWindow.tooltipDisplaying = true;
  3063. unsafeWindow.displayPlacementMessage("You don't have a free inventory slot!", mousePos[0] + 10, mousePos[1] + 10, "ERROR");
  3064. }
  3065. } else {
  3066. //If action is "BuyService" and the player doesn't have enough cash,
  3067. //don't fo anything
  3068. if (action == "UpdateTooltip") {
  3069. unsafeWindow.tooltipDisplaying = true;
  3070. unsafeWindow.displayPlacementMessage("You don't have enough cash to use this service!", mousePos[0] + 10, mousePos[1] + 10, "ERROR");
  3071. }
  3072. }
  3073. }
  3074. }
  3075. }
  3076.  
  3077. function quickServiceArmorMakeChain() {
  3078. var invFreeSlotNumber = unsafeWindow.findFirstEmptyGenericSlot("inv");
  3079. if (invFreeSlotNumber === false) {
  3080. return -1;
  3081. }
  3082. var chainParams = {}
  3083. //Craft the shared params and clone them to all the requests
  3084. chainParams["unequip"] = {}
  3085. chainParams["unequip"]["creditsnum"] = userVars["DFSTATS_df_credits"];
  3086. chainParams["unequip"]["buynum"] = "0";
  3087. chainParams["unequip"]["renameto"] = "undefined`undefined";
  3088. chainParams["unequip"]["expected_itemprice"] = "-1";
  3089. chainParams["unequip"]["price"] = unsafeWindow.getUpgradePrice();
  3090.  
  3091. chainParams["equip"] = cloneObject(chainParams["unequip"]);
  3092.  
  3093. chainParams["unequip"]["expected_itemtype2"] = userVars["DFSTATS_df_armourtype"];
  3094. chainParams["unequip"]["expected_itemtype"] = "";
  3095. chainParams["unequip"]["itemnum2"] = 34;
  3096. chainParams["unequip"]["itemnum"] = invFreeSlotNumber;
  3097. chainParams["unequip"]["action"] = "newequip";
  3098.  
  3099. chainParams["equip"]["expected_itemtype2"] = "";
  3100. chainParams["equip"]["expected_itemtype"] = userVars["DFSTATS_df_armourtype"];
  3101. chainParams["equip"]["itemnum2"] = 34;
  3102. chainParams["equip"]["itemnum"] = invFreeSlotNumber;
  3103. chainParams["equip"]["action"] = "newequip";
  3104.  
  3105. return chainParams;
  3106. }
  3107.  
  3108. async function quickServiceArmorBuyService() {
  3109.  
  3110. closeHelpWindowPage();
  3111. unsafeWindow.pageLock = true;
  3112. helpWindow.innerHTML = "<div style='text-align: center'>Loading, please wait...</div>";
  3113. helpWindow.parentNode.style.display = "block";
  3114.  
  3115. var invFreeSlotNumber = unsafeWindow.findFirstEmptyGenericSlot("inv");
  3116. var chainParams = quickServiceArmorMakeChain()
  3117.  
  3118. //Unequip armor
  3119. await makeRequest("https://fairview.deadfrontier.com/onlinezombiemmo/inventory_new.php", chainParams["unequip"], updateInventoryData, null);
  3120. //Repair armor
  3121. await buyService(invFreeSlotNumber);
  3122. //Equip repaired armor
  3123. await makeRequest("https://fairview.deadfrontier.com/onlinezombiemmo/inventory_new.php", chainParams["equip"], updateInventoryData, null);
  3124.  
  3125. closeHelpWindowPage();
  3126.  
  3127. }
  3128.  
  3129. async function quickServiceItemHelper(action, category) {
  3130. //Show custom box the icon is hovered whilst the ALT is pressed
  3131. var mousePos = unsafeWindow.mousePos;
  3132. var playerCash = userVars["DFSTATS_df_cash"];
  3133. //Make sure it makes sense to use the QuickService
  3134. if (
  3135. (category == "Medical" && parseInt(userVars["DFSTATS_df_hpcurrent"]) < parseInt(userVars["DFSTATS_df_hpmax"])) ||
  3136. (category == "Food" && parseInt(userVars['DFSTATS_df_hungerhp']) < 100)
  3137. ) {
  3138.  
  3139. //Refresh tradeInfo
  3140. var itemId, needsAdminister;
  3141. if (category == "Medical") {
  3142. [itemId, needsAdminister] = getBestLevelAppropriateMedicalTypeAndAdministerNeed();
  3143. } else {
  3144. itemId = getBestLevelAppropriateFoodType();
  3145. }
  3146. var result = await requestItem(itemsDataBank[itemId]);
  3147. if (!result) {
  3148. alert("The request went wrong. You might be rate limited. Try again in a minute. If the issue persists, contact SilverBeam.");
  3149. return;
  3150. }
  3151.  
  3152. //Check that at least 1 service is available if trying to heal
  3153. if (category == "Medical" && servicesDataBank["Doctor"][itemsDataBank[itemId].professionLevel][0] == undefined) {
  3154. unsafeWindow.tooltipDisplaying = true;
  3155. unsafeWindow.displayPlacementMessage("No available Doctors were found!", mousePos[0] + 10, mousePos[1] + 10, "ERROR");
  3156. return;
  3157. }
  3158.  
  3159. var slotNumber = findLastEmptyGenericSlot("inv");
  3160. var itemPrice = itemsDataBank[itemId]["trades"][0]["price"];
  3161. if (category == "Medical" && needsAdminister) {
  3162. itemPrice += servicesDataBank["Doctor"][itemsDataBank[itemId].professionLevel][0]["price"];
  3163. }
  3164.  
  3165. if (itemPrice <= playerCash) {
  3166. if (slotNumber != false) {
  3167. if (action == "UpdateTooltip") {
  3168. unsafeWindow.tooltipDisplaying = true;
  3169. unsafeWindow.displayPlacementMessage("Refill", mousePos[0] + 10, mousePos[1] + 10, "ACTION");
  3170. } else if (action == "BuyService") {
  3171. if (category == "Medical") {
  3172. quickServiceMedicalBuyItemAndService();
  3173. }
  3174. if (category == "Food") {
  3175. quickServiceFoodBuyItemAndEat();
  3176. }
  3177. }
  3178. } else {
  3179. unsafeWindow.tooltipDisplaying = true;
  3180. unsafeWindow.displayPlacementMessage("You don't have a free inventory slot!", mousePos[0] + 10, mousePos[1] + 10, "ERROR");
  3181. }
  3182. } else {
  3183. //If action is "BuyService" and the player doesn't have enough cash,
  3184. //don't fo anything
  3185. if (action == "UpdateTooltip") {
  3186. unsafeWindow.tooltipDisplaying = true;
  3187. unsafeWindow.displayPlacementMessage("You don't have enough cash to use this service!", mousePos[0] + 10, mousePos[1] + 10, "ERROR");
  3188. }
  3189. }
  3190. }
  3191. }
  3192.  
  3193. async function quickServiceMedicalBuyItemAndService() {
  3194.  
  3195. closeHelpWindowPage();
  3196. unsafeWindow.pageLock = true;
  3197. helpWindow.innerHTML = "<div style='text-align: center'>Loading, please wait...</div>";
  3198. helpWindow.parentNode.style.display = "block";
  3199.  
  3200. var invFreeSlotNumber = findLastEmptyGenericSlot("inv");
  3201. var [itemId, needsAdminister] = getBestLevelAppropriateMedicalTypeAndAdministerNeed();
  3202.  
  3203. //Buy the item
  3204. var result = await buyItem(itemId);
  3205. if (!result) {
  3206. helpWindow.innerHTML = "<div style='text-align: center'>Something went wrong</div>";
  3207. setTimeout(closeHelpWindowPage, 5000);
  3208. }
  3209. if (needsAdminister) {
  3210. //Administer the item
  3211. await buyService(invFreeSlotNumber);
  3212. } else {
  3213. //Use the item
  3214. var params = {}
  3215. params["creditsnum"] = "0";
  3216. params["buynum"] = "0";
  3217. params["renameto"] = "undefined`undefined";
  3218. params["expected_itemprice"] = "-1";
  3219. params["expected_itemtype2"] = "";
  3220. params["expected_itemtype"] = itemId;
  3221. params["itemnum2"] = "0";
  3222. params["itemnum"] = invFreeSlotNumber;
  3223. params["price"] = "0";
  3224. params["action"] = "newuse";
  3225.  
  3226. await makeRequest("https://fairview.deadfrontier.com/onlinezombiemmo/inventory_new.php", params, updateInventoryData, null);
  3227.  
  3228. unsafeWindow.playSound("heal");
  3229.  
  3230. }
  3231.  
  3232. closeHelpWindowPage();
  3233.  
  3234. }
  3235.  
  3236. async function quickServiceFoodBuyItemAndEat() {
  3237.  
  3238. closeHelpWindowPage();
  3239. unsafeWindow.pageLock = true;
  3240. helpWindow.innerHTML = "<div style='text-align: center'>Loading, please wait...</div>";
  3241. helpWindow.parentNode.style.display = "block";
  3242.  
  3243. var invFreeSlotNumber = findLastEmptyGenericSlot("inv");
  3244. var itemId = getBestLevelAppropriateFoodType();
  3245.  
  3246. //Buy the item
  3247. var result = await buyItem(itemId);
  3248. if (!result) {
  3249. helpWindow.innerHTML = "<div style='text-align: center'>Something went wrong</div>";
  3250. setTimeout(closeHelpWindowPage, 5000);
  3251. }
  3252.  
  3253. //Eat the item
  3254. //Craft the params
  3255. var params = {}
  3256. params["creditsnum"] = "0";
  3257. params["buynum"] = "0";
  3258. params["renameto"] = "undefined`undefined";
  3259. params["expected_itemprice"] = "-1";
  3260. params["expected_itemtype2"] = "";
  3261. params["expected_itemtype"] = itemId;
  3262. params["itemnum2"] = "0";
  3263. params["itemnum"] = invFreeSlotNumber;
  3264. params["price"] = "0";
  3265. params["action"] = "newconsume";
  3266.  
  3267. await makeRequest("https://fairview.deadfrontier.com/onlinezombiemmo/inventory_new.php", params, updateInventoryData, null);
  3268.  
  3269. unsafeWindow.playSound("eat");
  3270.  
  3271. closeHelpWindowPage();
  3272.  
  3273. }
  3274.  
  3275. //////////////////////////////
  3276. // Quick Character Swapper ///
  3277. //////////////////////////////
  3278.  
  3279. function clearCookies() {
  3280. var cookies = document.cookie.split(';');
  3281. //Clear seen ad banners
  3282. for (var i in cookies) {
  3283. var vals = cookies[i].split('=');
  3284. var name = vals.shift(0, 1).trim();
  3285. if (name.includes("_seen_brief")) {
  3286. document.cookie = name + '=; Path=/onlinezombiemmo;Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
  3287. }
  3288. }
  3289. //Clear the actual login cookie and the lastuser
  3290. document.cookie = 'DeadFrontierFairview=; Path=/;Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
  3291. document.cookie = 'lastLoginUser=; Path=/onlinezombiemmo;Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
  3292. }
  3293.  
  3294. function clearAndSetCookies(cookie) {
  3295. clearCookies();
  3296. var cookies = cookie.split(';');
  3297. for (var i in cookies) {
  3298. var vals = cookies[i].split('=');
  3299. var name = vals.shift(0, 1).trim();
  3300. if (name.includes("_seen_brief") || name.includes("lastLoginUser")) {
  3301. //Restore seen ad banners
  3302. document.cookie = name + '=' + vals.join('=') + '; Path=/onlinezombiemmo;max-age=31536000;';
  3303. } else if (name == 'DeadFrontierFairview') {
  3304. //Restore the actual login cookie
  3305. document.cookie = name + '=' + vals.join('=') + '; Path=/;max-age=31536000;';
  3306. }
  3307. }
  3308. }
  3309.  
  3310. function changeCharacter(cookies) {
  3311. clearAndSetCookies(cookies);
  3312. window.open("https://fairview.deadfrontier.com/onlinezombiemmo/index.php", "_self");
  3313. }
  3314.  
  3315. //////////////////////
  3316. // Sidebar Expanded //
  3317. //////////////////////
  3318.  
  3319. function moveTooltipAndGrabbedItemOnTop() {
  3320. //Make this only trigger if an inventory is available
  3321. if (!isAtLocation("inventories") || isAtLocation("ICInventory")) {
  3322. return;
  3323. }
  3324. //Bring tooltip on top
  3325. var tooltip = document.getElementById("textAddon");
  3326. var newParent = tooltip.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode;
  3327. tooltip.style.position = "absolute";
  3328. tooltip.style.fontFamily = "Courier New,Arial";
  3329. tooltip.style.fontWeight = 600;
  3330. tooltip.style.textAlign = "center";
  3331. tooltip.style.zIndex = 20;
  3332. newParent.id = "gameWindow";
  3333. newParent.style.position = "relative";
  3334. newParent.appendChild(tooltip);
  3335.  
  3336. //Update tooltip display function
  3337. unsafeWindow.vanillaDisplayPlacementMessage = unsafeWindow.displayPlacementMessage;
  3338. unsafeWindow.displayPlacementMessage = hijackedDisplayPlacementMessage;
  3339.  
  3340. //Update tooltip clean function
  3341. unsafeWindow.vanillaCleanPlacementMessage = unsafeWindow.cleanPlacementMessage;
  3342. unsafeWindow.cleanPlacementMessage = hijackedCleanPlacementMessage;
  3343.  
  3344. //Update item drag functions
  3345. unsafeWindow.inventoryHolder.removeEventListener("mousemove", unsafeWindow.drag);
  3346. newParent.addEventListener("mousemove", hijackedFakeItemDrag);
  3347.  
  3348. //Bring fake grabbed item on top
  3349. var fakeGrabbedItem = document.getElementById("fakeGrabbedItem");
  3350. fakeGrabbedItem.style.position = "absolute";
  3351. fakeGrabbedItem.style.display = "none";
  3352. fakeGrabbedItem.style.width = "40px";
  3353. fakeGrabbedItem.style.height = "40px";
  3354. fakeGrabbedItem.style.opacity = 0.6;
  3355. newParent.appendChild(fakeGrabbedItem);
  3356.  
  3357. //Add item interaction div to the sidebar
  3358. var interactionWindow = document.createElement("div");
  3359. interactionWindow.style.position = "absolute";
  3360. interactionWindow.style.width = "85px";
  3361. interactionWindow.style.height = "270px";
  3362. interactionWindow.style.left = "0px"
  3363. interactionWindow.style.top = "80px";
  3364. interactionWindow.dataset.action = "giveToChar";
  3365. interactionWindow.className = "fakeSlot";
  3366. document.getElementById("sidebar").appendChild(interactionWindow);
  3367. }
  3368.  
  3369. //Hijack the tooltip display function to temporarily swap what the game believes to be the inventory holder.
  3370. //This is needed in order to circumvent the inventory bounding box check.
  3371. function hijackedDisplayPlacementMessage(msg, x, y, type) {
  3372. var gameWindow = document.getElementById("gameWindow");
  3373. var oldInventoryHolder = unsafeWindow.inventoryHolder;
  3374. unsafeWindow.inventoryHolder = gameWindow;
  3375. unsafeWindow.vanillaDisplayPlacementMessage(msg, x, y, type);
  3376. unsafeWindow.inventoryHolder = oldInventoryHolder;
  3377. }
  3378.  
  3379. //Hijack the tooltip clean function. This is needed because the new hover function cleans the tooltip
  3380. //on mouse move in the whole window.
  3381. function hijackedCleanPlacementMessage() {
  3382. if (!unsafeWindow.tooltipDisplaying) {
  3383. unsafeWindow.vanillaCleanPlacementMessage();
  3384. }
  3385. }
  3386.  
  3387. //Hijack the drag function to temporarily swap what the game believes to be the inventory holder.
  3388. //This is needed in order to circumvent the inventory bounding box check.
  3389. function hijackedFakeItemDrag(e) {
  3390. var gameWindow = document.getElementById("gameWindow");
  3391. var oldInventoryHolder = unsafeWindow.inventoryHolder;
  3392. unsafeWindow.inventoryHolder = gameWindow;
  3393. unsafeWindow.drag(e);
  3394. unsafeWindow.inventoryHolder = oldInventoryHolder;
  3395. }
  3396.  
  3397. //Hide the tooltip if unsafeWindow.tooltipDisplaying is false
  3398. function cleanTooltipIfNeeded() {
  3399. if (unsafeWindow.tooltipDisplaying) {
  3400. unsafeWindow.tooltipDisplaying = false;
  3401. unsafeWindow.cleanPlacementMessage();
  3402. }
  3403. }
  3404.  
  3405. //////////////
  3406. // Konami ///
  3407. /////////////
  3408.  
  3409. function addKonami() {
  3410. if (!isAtLocation("home")) {
  3411. return;
  3412. }
  3413.  
  3414. var konamiList = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'KeyB', 'KeyA'];
  3415. var konamiPosition = 0;
  3416.  
  3417. document.addEventListener('keydown', function(e) {
  3418. if (konamiPosition == -1) {
  3419. return;
  3420. }
  3421. var key = e.code;
  3422. var requiredKey = konamiList[konamiPosition];
  3423. if (key == requiredKey) {
  3424. konamiPosition++;
  3425. if (konamiPosition == konamiList.length) {
  3426. konamiPosition = -1;
  3427. unsafeWindow.playSound("heal");
  3428. activateDisco();
  3429. }
  3430. } else {
  3431. konamiPosition = 0;
  3432. }
  3433. });
  3434. }
  3435.  
  3436. function activateDisco() {
  3437. var disco = new Audio("https://dl.sndup.net/v3pvv/ouch.mp3");
  3438. disco.play();
  3439. }
  3440.  
  3441. //////////////////////////////
  3442. // DOM Event Listeners //
  3443. //////////////////////////////
  3444.  
  3445.  
  3446. function registerEventListeners() {
  3447. //Inventories
  3448. if (isAtLocation("inventories")) {
  3449. var inventorySlots = [...document.getElementsByClassName("validSlot")].filter(node => node.parentNode.parentNode.id == "inventory");
  3450. for (var slot of inventorySlots) {
  3451. slot.addEventListener("mouseenter", mouseEnterSlotHandler);
  3452. slot.addEventListener("mouseleave", mouseLeaveSlotHandler);
  3453. slot.addEventListener("mousemove", showTooltipHandler);
  3454. slot.addEventListener("mouseup", mouseUpSlotHandler, true);
  3455. }
  3456. var inventoryTable = document.getElementById("inventory");
  3457. inventoryTable.addEventListener("mouseenter", mouseEnterInventoryHandler);
  3458. inventoryTable.addEventListener("mouseleave", mouseLeaveInventoryHandler);
  3459.  
  3460. window.addEventListener("keydown", showTooltipHandler);
  3461. window.addEventListener("keyup", windowKeyUpHandler);
  3462. }
  3463.  
  3464. //Marketplace
  3465. if (isAtLocation("marketplace")) {
  3466. registerTabSwitchHandlers();
  3467. registerQuickServiceHandlers();
  3468. }
  3469.  
  3470. //Save market data on page exit
  3471. window.addEventListener("beforeunload", saveMarketData);
  3472. }
  3473.  
  3474. //Register handlers to move menu depending on market tab
  3475. function registerTabSwitchHandlers() {
  3476. var marketBuyingTab = document.getElementById("loadBuying");
  3477. var marketSellingTab = document.getElementById("loadSelling");
  3478. var marketPrivateTab = document.getElementById("loadPrivate");
  3479. var marketTradingTab = document.getElementById("loadItemForItem");
  3480.  
  3481. //Return if we are in a submenu, like the ones in the item-for-item tab
  3482. if (marketBuyingTab == null) {
  3483. return;
  3484. }
  3485.  
  3486. marketBuyingTab.addEventListener("click", exitTradingClickHandler);
  3487. marketBuyingTab.addEventListener("click", registerMarketListObserver);
  3488. marketSellingTab.addEventListener("click", exitTradingClickHandler);
  3489. marketPrivateTab.addEventListener("click", enterTradingClickHandler);
  3490. marketTradingTab.addEventListener("click", enterTradingClickHandler);
  3491. }
  3492.  
  3493. //Register QuickService handlers
  3494. function registerQuickServiceHandlers() {
  3495. var sidebarArmourIcon = document.getElementById("sidebarArmour");
  3496.  
  3497. sidebarArmourIcon.addEventListener("mousemove", showQuickServiceArmorTooltipHandler);
  3498. sidebarArmourIcon.addEventListener("mouseup", mouseUpQuickServiceArmorHandler, true);
  3499. sidebarArmourIcon.addEventListener("mouseleave", mouseLeaveQuickServiceArmorHandler);
  3500.  
  3501. var sidebarHealthText = document.getElementsByClassName("playerHealth")[0];
  3502. var sidebarHealthIcon = document.getElementsByClassName("playerHealth")[0].parentNode.firstChild;
  3503.  
  3504. sidebarHealthText.addEventListener("mousemove", showQuickServiceMedicalTooltipHandler);
  3505. sidebarHealthIcon.addEventListener("mousemove", showQuickServiceMedicalTooltipHandler);
  3506. sidebarHealthText.addEventListener("mouseup", mouseUpQuickServiceMedicalHandler, true);
  3507. sidebarHealthIcon.addEventListener("mouseup", mouseUpQuickServiceMedicalHandler, true);
  3508. sidebarHealthText.addEventListener("mouseleave", mouseLeaveQuickServiceMedicalHandler);
  3509. sidebarHealthIcon.addEventListener("mouseleave", mouseLeaveQuickServiceMedicalHandler);
  3510.  
  3511. var sidebarFoodText = document.getElementsByClassName("playerNourishment")[0];
  3512. var sidebarFoodIcon = document.getElementsByClassName("playerNourishment")[0].parentNode.firstChild;
  3513.  
  3514. sidebarFoodText.addEventListener("mousemove", showQuickServiceFoodTooltipHandler);
  3515. sidebarFoodIcon.addEventListener("mousemove", showQuickServiceFoodTooltipHandler);
  3516. sidebarFoodText.addEventListener("mouseup", mouseUpQuickServiceFoodHandler, true);
  3517. sidebarFoodIcon.addEventListener("mouseup", mouseUpQuickServiceFoodHandler, true);
  3518. sidebarFoodText.addEventListener("mouseleave", mouseLeaveQuickServiceFoodHandler);
  3519. sidebarFoodIcon.addEventListener("mouseleave", mouseLeaveQuickServiceFoodHandler);
  3520. }
  3521.  
  3522. //Detect which slot has been entered
  3523. function mouseEnterSlotHandler(e) {
  3524. var slot = e.target.dataset.slot;
  3525. if (lastSlotHovered != slot) {
  3526. lastSlotHovered = slot;
  3527. }
  3528. //Fetch market info on item, if present
  3529. if (e.target.childNodes.length > 0) {
  3530. var itemName = e.target.childNodes[0].dataset.type.split("_")[0];
  3531. var itemExtraInfo = e.target.childNodes[0].dataset.type.split("_")[1];
  3532. //Fix for cooked items detection
  3533. if (itemExtraInfo == "cooked") {
  3534. itemName = itemName + "_cooked";
  3535. }
  3536. requestItem(itemsDataBank[itemName]);
  3537. }
  3538. }
  3539.  
  3540. //Fix hoverbox popping when DOM is injected by only showing hoverBox after
  3541. //content is injected
  3542. function mouseLeaveSlotHandler(e) {
  3543. infoBox.style.opacity = 0;
  3544.  
  3545. cleanTooltipIfNeeded();
  3546. }
  3547.  
  3548. function mouseUpSlotHandler(e) {
  3549. //This service should only be available in the market
  3550. if (!isAtLocation("marketplace")) {
  3551. return;
  3552. }
  3553. //Check if autoService isn't disabled
  3554. if (e.altKey && lastSlotHovered != -1 && userSettings.autoService) {
  3555. var targetInventoryItem = inventoryArray[lastSlotHovered - 1];
  3556. if (havePendingRequestsCompleted()) {
  3557. autoServiceHelper(targetInventoryItem, "BuyService");
  3558. }
  3559. }
  3560. }
  3561.  
  3562. //Slot hover handler used to update tooltip location
  3563. function showTooltipHandler(e) {
  3564. //This service should only be available in the market
  3565. if (!isAtLocation("marketplace")) {
  3566. return;
  3567. }
  3568. //Check if autoService isn't disabled
  3569. if (e.altKey && lastSlotHovered != -1 && userSettings.autoService) {
  3570. var targetInventoryItem = inventoryArray[lastSlotHovered - 1];
  3571. if (havePendingRequestsCompleted()) {
  3572. autoServiceHelper(targetInventoryItem, "UpdateTooltip");
  3573. }
  3574. }
  3575. }
  3576.  
  3577. //Make hoverBox invisible untill DOM is injected to prevent popping
  3578. function mouseEnterInventoryHandler(e) {
  3579. infoBox.style.opacity = 0;
  3580. }
  3581.  
  3582. function mouseLeaveInventoryHandler(e) {
  3583. //Reset hoverBox visibility on exit
  3584. infoBox.style.opacity = 1;
  3585. //Reset hovered slot index when the inventory table is exited
  3586. lastSlotHovered = -1;
  3587. }
  3588.  
  3589. //Check and eventually clean the tooltip
  3590. function windowKeyUpHandler(e) {
  3591. //This service should only be available in the market
  3592. if (!isAtLocation("marketplace")) {
  3593. return;
  3594. }
  3595. //Check if autoService isn't disabled
  3596. if (e.key == "Alt" && userSettings.autoService) {
  3597. e.preventDefault(); //We don't want the browser to focus out of the window
  3598. document.getElementById("sidebarArmour").style.cursor = "default";
  3599.  
  3600. cleanTooltipIfNeeded();
  3601. }
  3602. }
  3603.  
  3604. function helpMenuClickHandler(e) {
  3605. openHelpWindowPage("home");
  3606. }
  3607.  
  3608. function loadoutsClickHandler(e) {
  3609. openHelpWindowPage("loadoutsMenu");
  3610. }
  3611.  
  3612. //Move the menu button in the item-for-item page
  3613. function enterTradingClickHandler(e) {
  3614. document.getElementById("silverscriptsMenuButton").style.right = "100px";
  3615. }
  3616.  
  3617. //Move the menu button after exiting the item-for-item page
  3618. function exitTradingClickHandler(e) {
  3619. document.getElementById("silverscriptsMenuButton").style.right = "20px";
  3620. }
  3621.  
  3622. //Save to storage the market data across page refreshes
  3623. async function saveMarketData() {
  3624. savedMarketData["itemsDataBank"] = itemsDataBank;
  3625. savedMarketData["servicesDataBank"] = servicesDataBank;
  3626. await GM.setValue("savedMarketData", JSON.stringify(savedMarketData));
  3627. }
  3628.  
  3629. function showQuickServiceArmorTooltipHandler(e) {
  3630. //This service should only be available in the market
  3631. if (!isAtLocation("marketplace")) {
  3632. return;
  3633. }
  3634. //Check if autoService isn't disabled
  3635. if (e.altKey && userSettings.autoService) {
  3636. if (havePendingRequestsCompleted()) {
  3637. if (userVars['DFSTATS_df_armourtype'].split("_")[0] != '' &&
  3638. parseInt(userVars['DFSTATS_df_armourhp']) < parseInt(userVars['DFSTATS_df_armourhpmax'])) {
  3639. document.getElementById("sidebarArmour").style.cursor = "grab";
  3640. quickServiceArmorHelper("UpdateTooltip");
  3641. }
  3642. }
  3643. }
  3644. }
  3645.  
  3646. function mouseUpQuickServiceArmorHandler(e) {
  3647. //This service should only be available in the market
  3648. if (!isAtLocation("marketplace")) {
  3649. return;
  3650. }
  3651. document.getElementById("sidebarArmour").style.cursor = "default";
  3652.  
  3653. cleanTooltipIfNeeded();
  3654.  
  3655. //Check if autoService isn't disabled
  3656. if (e.altKey && userSettings.autoService) {
  3657. if (havePendingRequestsCompleted()) {
  3658. quickServiceArmorHelper("BuyService");
  3659. }
  3660. }
  3661. }
  3662.  
  3663. function mouseLeaveQuickServiceArmorHandler(e) {
  3664. document.getElementById("sidebarArmour").style.cursor = "default";
  3665.  
  3666. cleanTooltipIfNeeded();
  3667. }
  3668.  
  3669. function showQuickServiceMedicalTooltipHandler(e) {
  3670. //This service should only be available in the market
  3671. if (!isAtLocation("marketplace")) {
  3672. return;
  3673. }
  3674. //Check if autoService isn't disabled
  3675. if (e.altKey && userSettings.autoService) {
  3676. if (havePendingRequestsCompleted()) {
  3677. if (parseInt(userVars["DFSTATS_df_hpcurrent"]) < parseInt(userVars["DFSTATS_df_hpmax"])) {
  3678. document.getElementsByClassName("playerHealth")[0].style.cursor = "grab";
  3679. document.getElementsByClassName("playerHealth")[0].parentNode.firstChild.style.cursor = "grab";
  3680. quickServiceItemHelper("UpdateTooltip", "Medical");
  3681. }
  3682. }
  3683. }
  3684. }
  3685.  
  3686. function mouseUpQuickServiceMedicalHandler(e) {
  3687. //This service should only be available in the market
  3688. if (!isAtLocation("marketplace")) {
  3689. return;
  3690. }
  3691. document.getElementsByClassName("playerHealth")[0].style.cursor = "default";
  3692. document.getElementsByClassName("playerHealth")[0].parentNode.firstChild.style.cursor = "default";
  3693.  
  3694. cleanTooltipIfNeeded();
  3695.  
  3696. //Check if autoService isn't disabled
  3697. if (e.altKey && userSettings.autoService) {
  3698. if (havePendingRequestsCompleted()) {
  3699. if (parseInt(userVars["DFSTATS_df_hpcurrent"]) < parseInt(userVars["DFSTATS_df_hpmax"])) {
  3700. quickServiceItemHelper("BuyService", "Medical");
  3701. }
  3702. }
  3703. }
  3704. }
  3705.  
  3706. function mouseLeaveQuickServiceMedicalHandler(e) {
  3707. document.getElementsByClassName("playerHealth")[0].style.cursor = "default";
  3708. document.getElementsByClassName("playerHealth")[0].parentNode.firstChild.style.cursor = "default";
  3709.  
  3710. cleanTooltipIfNeeded();
  3711. }
  3712.  
  3713. function showQuickServiceFoodTooltipHandler(e) {
  3714. //This service should only be available in the market
  3715. if (!isAtLocation("marketplace")) {
  3716. return;
  3717. }
  3718. //Check if autoService isn't disabled
  3719. if (e.altKey && userSettings.autoService) {
  3720. if (havePendingRequestsCompleted()) {
  3721. if (parseInt(userVars['DFSTATS_df_hungerhp']) < 100) {
  3722. document.getElementsByClassName("playerNourishment")[0].style.cursor = "grab";
  3723. document.getElementsByClassName("playerNourishment")[0].parentNode.firstChild.style.cursor = "grab";
  3724. quickServiceItemHelper("UpdateTooltip", "Food");
  3725. }
  3726. }
  3727. }
  3728. }
  3729.  
  3730. function mouseUpQuickServiceFoodHandler(e) {
  3731. //This service should only be available in the market
  3732. if (!isAtLocation("marketplace")) {
  3733. return;
  3734. }
  3735. document.getElementsByClassName("playerNourishment")[0].style.cursor = "default";
  3736. document.getElementsByClassName("playerNourishment")[0].parentNode.firstChild.style.cursor = "default";
  3737.  
  3738. cleanTooltipIfNeeded();
  3739.  
  3740. //Check if autoService isn't disabled
  3741. if (e.altKey && userSettings.autoService) {
  3742. if (havePendingRequestsCompleted()) {
  3743. if (parseInt(userVars['DFSTATS_df_hungerhp']) < 100) {
  3744. quickServiceItemHelper("BuyService", "Food");
  3745. }
  3746. }
  3747. }
  3748. }
  3749.  
  3750. function mouseLeaveQuickServiceFoodHandler(e) {
  3751. document.getElementsByClassName("playerNourishment")[0].style.cursor = "default";
  3752. document.getElementsByClassName("playerNourishment")[0].parentNode.firstChild.style.cursor = "default";
  3753.  
  3754. cleanTooltipIfNeeded();
  3755. }
  3756.  
  3757. //////////////////////
  3758. // DOM Observers //
  3759. /////////////////////
  3760.  
  3761. function registerDOMObservers() {
  3762. registerHoverBoxObserver();
  3763. registerInventoryObserver();
  3764. registerMarketObserver();
  3765. registerMarketListObserver();
  3766. registerStorageObserver();
  3767. //registerMarketSidebarObserver();
  3768. }
  3769.  
  3770. function registerHoverBoxObserver() {
  3771. //This service should only be available in inventories
  3772. if (!isAtLocation("inventories")) {
  3773. return;
  3774. }
  3775. var observerTargetNode = unsafeWindow.infoBox;
  3776. var mutationConfig = {
  3777. childList: true,
  3778. subtree: true
  3779. };
  3780.  
  3781. var hoverBoxMutationCallback = function(mutationList, observer) {
  3782. //Only listen for childList mutations
  3783. for (var mutation of mutationList) {
  3784. if (mutation.type === 'childList') {
  3785. //Detect the class of the children. If any has "itemName", then this is a vanilla js mutation
  3786. var isVanillaMutation = Object.values(mutation.addedNodes).some(node => node.className === "itemName");
  3787. if (isVanillaMutation && lastSlotHovered != -1) {
  3788. //We are already catching the current slot number via the mouseEnter eventListener,
  3789. //which always fires before the vanilla mutation occurs
  3790. fillHoverBox();
  3791. break;
  3792. }
  3793. }
  3794. }
  3795. };
  3796.  
  3797. var hoverBoxObserver = new MutationObserver(hoverBoxMutationCallback);
  3798. hoverBoxObserver.observe(observerTargetNode, mutationConfig);
  3799. }
  3800.  
  3801. function registerInventoryObserver() {
  3802. //This service should only be available in inventories
  3803. if (!isAtLocation("inventories")) {
  3804. return;
  3805. }
  3806. var observerTargetNode = document.getElementById("inventory");
  3807. var mutationConfig = {
  3808. childList: true,
  3809. subtree: true
  3810. };
  3811.  
  3812. var inventoryMutationCallback = function(mutationList, observer) {
  3813. //Update inventory and databank info only if inventory mutated,
  3814. //and only if mutation happened at least pendingRequests.requestsCooldownPeriod milliseconds
  3815. //after the last one
  3816. //We must wait until all the mutations have occured onto the inventory before updating,
  3817. //thus 500ms are waited before fetching new data
  3818. setTimeout(function() {
  3819. if (!pendingRequests.requestsCoolingDown) {
  3820. initInventoryArray();
  3821. resetDataBankItemsMarketInfo();
  3822. refreshServicesDataBank();
  3823. pendingRequests.requestsCoolingDown = true;
  3824. setTimeout(function() {
  3825. pendingRequests.requestsCoolingDown = false;
  3826. }, pendingRequests.requestsCooldownPeriod);
  3827. }
  3828. }, 500);
  3829. };
  3830.  
  3831. var inventoryObserver = new MutationObserver(inventoryMutationCallback);
  3832. inventoryObserver.observe(observerTargetNode, mutationConfig);
  3833. }
  3834.  
  3835. function registerMarketObserver() {
  3836. //This service should only be available in the market
  3837. if (!isAtLocation("marketplace")) {
  3838. return;
  3839. }
  3840. var observerTargetNode = document.getElementById("marketplace");
  3841. var mutationConfig = {
  3842. childList: true,
  3843. subtree: true
  3844. };
  3845.  
  3846. var marketMutationCallback = function(mutationList, observer) {
  3847. //It seems that whenever a tab is switched, all listeners get unregistered. Register them again.
  3848. //The menu needs to be fixed only in the market
  3849. if (isAtLocation("marketplace")) {
  3850. registerTabSwitchHandlers();
  3851. }
  3852. };
  3853.  
  3854. var marketObserver = new MutationObserver(marketMutationCallback);
  3855. marketObserver.observe(observerTargetNode, mutationConfig);
  3856. }
  3857.  
  3858. function registerMarketListObserver() {
  3859. //This service should only be available in the market
  3860. if (!isAtLocation("marketplace")) {
  3861. return;
  3862. }
  3863. var observerTargetNode = document.getElementById("itemDisplay");
  3864. var mutationConfig = {
  3865. childList: true,
  3866. subtree: true
  3867. };
  3868.  
  3869. var marketListMutationCallback = function(mutationList, observer) {
  3870. //Check if the service is enabled
  3871. if (!userSettings.autoMarketWithdraw) {
  3872. return;
  3873. }
  3874. //Check if the user is in the "buy" market tab
  3875. if (unsafeWindow.marketScreen == "buy") {
  3876. for (var mutation of mutationList) {
  3877. if (mutation.addedNodes.length > 0) {
  3878. //We filter out our own changes
  3879. if (mutation.addedNodes[0].tagName != "BUTTON" && mutation.target.tagName != "BUTTON") {
  3880. injectAutoMarketWithdrawButton(mutation.addedNodes[0]);
  3881. }
  3882. }
  3883. }
  3884. }
  3885. };
  3886.  
  3887. var marketListObserver = new MutationObserver(marketListMutationCallback);
  3888. marketListObserver.observe(observerTargetNode, mutationConfig);
  3889. }
  3890.  
  3891. function registerStorageObserver() {
  3892. //This service should only be available in the storage
  3893. if (!isAtLocation("storage")) {
  3894. return;
  3895. }
  3896. var observerTargetNode = document.getElementById("normalContainer");
  3897. var mutationConfig = {
  3898. childList: true,
  3899. subtree: true
  3900. };
  3901.  
  3902. var storageMutationCallback = function(mutationList, observer) {
  3903. //Check if an item got taken away from a saved slot
  3904. if (mutationList.length <= 4) {
  3905. for (var mutation of mutationList) {
  3906. if (mutation.removedNodes.length > 0) {
  3907. var removedItemData = mutation.removedNodes[0].dataset;
  3908. var removedItemCategory = removedItemData.itemtype;
  3909. if (removedItemCategory in loadouts[0]) {
  3910. for (var i = 0; i < 4; i++) {
  3911. for (var j = 0; j < loadouts[i][removedItemCategory].length; j++) {
  3912. if (loadouts[i][removedItemCategory][j]["storageSlot"] == mutation.target.dataset.slot &&
  3913. loadouts[i][removedItemCategory][j]["itemFlashType"] == removedItemData.type) {
  3914. //An item registered to a loadout got moved. Reset its stats
  3915. loadouts[i][removedItemCategory][j]["storageSlot"] = -1;
  3916. loadouts[i][removedItemCategory][j]["itemFlashType"] = "";
  3917. loadouts[i][removedItemCategory][j]["itemCategory"] = "";
  3918. break;
  3919. }
  3920. }
  3921. }
  3922. }
  3923. }
  3924. }
  3925. }
  3926. for (mutation of mutationList) {
  3927. if (mutation.addedNodes.length > 0 && mutation.addedNodes[0].className != "silverScriptsCensor" && mutation.addedNodes[0].className != "silverScriptsLoadoutIndicator") {
  3928. loadoutAddUsedSlotOverlay(mutation.addedNodes[0].parentNode);
  3929. if (pickingLoadoutCategory != "") {
  3930. filterStorageSlotDuringPicking(mutation.addedNodes[0].parentNode);
  3931. }
  3932. }
  3933. }
  3934. };
  3935.  
  3936. var storageObserver = new MutationObserver(storageMutationCallback);
  3937. storageObserver.observe(observerTargetNode, mutationConfig);
  3938. }
  3939.  
  3940. function registerMarketSidebarObserver() {
  3941. //This service should only be available in the market
  3942. if (!isAtLocation("marketplace")) {
  3943. return;
  3944. }
  3945. var observerTargetNode = document.getElementById("sidebar");
  3946. var mutationConfig = {
  3947. childList: true,
  3948. subtree: true
  3949. };
  3950.  
  3951. var marketMutationCallback = function(mutationList, observer) {
  3952. //It seems that whenever a tab is switched, all listeners get unregistered. Register them again.
  3953. //The menu needs to be fixed only in the market
  3954. if (isAtLocation("marketplace")) {
  3955. registerQuickServiceHandlers();
  3956. }
  3957. };
  3958.  
  3959. var marketObserver = new MutationObserver(marketMutationCallback);
  3960. marketObserver.observe(observerTargetNode, mutationConfig);
  3961. }
  3962.  
  3963. //////////////////////////
  3964. // UI Update Functions //
  3965. //////////////////////////
  3966.  
  3967. //Fix hoverbox pointer events
  3968. function removeHoverBoxPointerEvents() {
  3969. //Check that an inventory is open
  3970. if (!isAtLocation("inventories")) {
  3971. return;
  3972. }
  3973. //Remove pointer events from injected UI and the hoverBox itself
  3974. //Style tag injection is needed because on Chrome the only 2 stylesheets
  3975. //available in the inner city inventory page are read-write protected
  3976. var sheet = document.createElement("style");
  3977. sheet.innerText = ".silverStats { pointer-events: none; }";
  3978. document.head.appendChild(sheet)
  3979. infoBox.style.pointerEvents = "none";
  3980. //The fake grabbed item sometimes confuses the browser.
  3981. var grabbedItem = document.getElementById("fakeGrabbedItem");
  3982. grabbedItem.style.pointerEvents = "none";
  3983. //Make sure the hoverBox the closest element to the screeen while we're at it
  3984. infoBox.style.zIndex = 1000;
  3985. }
  3986.  
  3987. //Add help text prompt
  3988. function addHelpButton() {
  3989. //Check that an inventory is open
  3990. if (!isAtLocation("inventories")) {
  3991. return;
  3992. }
  3993. var inventoryHolder = document.getElementById("inventoryholder");
  3994. var helpButton = document.createElement("button");
  3995. helpButton.textContent = "SilverScripts Menu";
  3996. helpButton.id = "silverscriptsMenuButton";
  3997. helpButton.className = "opElem";
  3998. helpButton.style.bottom = "86px";
  3999. helpButton.style.right = "20px";
  4000. helpButton.addEventListener("click", helpMenuClickHandler)
  4001. inventoryHolder.appendChild(helpButton);
  4002. }
  4003.  
  4004. //Add loadout button if the storage is open
  4005. function addLoadoutButton() {
  4006. //Check that the storage is open
  4007. if (!isAtLocation("storage")) {
  4008. return;
  4009. }
  4010. var inventoryHolder = document.getElementById("inventoryholder");
  4011. var loadoutButton = document.createElement("button");
  4012. loadoutButton.textContent = "Quickswap Menu";
  4013. loadoutButton.className = "opElem";
  4014. loadoutButton.style.bottom = "86px";
  4015. loadoutButton.style.right = "180px";
  4016. loadoutButton.addEventListener("click", openLoadoutsMenu)
  4017. inventoryHolder.appendChild(loadoutButton);
  4018. }
  4019.  
  4020. //Container used to display various buttons such as the QuickSwitcher
  4021. function addTopMenuContainer() {
  4022. let mainSelect = document.body;
  4023. let topMenuContainer = document.createElement("div");
  4024. topMenuContainer.id = "silverScriptsTopMenuContainer";
  4025. topMenuContainer.style.display = "grid";
  4026. topMenuContainer.style.rowGap = "5px";
  4027. topMenuContainer.style.position = "fixed";
  4028. topMenuContainer.style.top = "18px";
  4029. //Move to the left in inventories for compatibility with Rebekah's Scripts
  4030. if (isAtLocation("inventories")) {
  4031. topMenuContainer.style.left = "2px";
  4032. } else {
  4033. topMenuContainer.style.right = "2px";
  4034. }
  4035. topMenuContainer.style.zIndex = "20";
  4036. mainSelect.appendChild(topMenuContainer);
  4037. }
  4038.  
  4039. //Add character quick switcher button if at home. Credit to Rebekah/Tectonic Stupidity for the UI design.
  4040. function addQuickSwitcherButton() {
  4041. //check that home is open
  4042. if ((!isAtLocation("home") && !userSettings.alwaysDisplayQuickSwitcher) ||
  4043. isAtLocation("ICInventory") || isAtLocation("innerCity")
  4044. ) {
  4045. return;
  4046. }
  4047.  
  4048. let topMenuContainer = document.getElementById("silverScriptsTopMenuContainer");
  4049. let container = document.createElement("div");
  4050. container.style.height = "max-content";
  4051. container.style.width = "max-content";
  4052. container.style.minWidth = "41px";
  4053. container.style.justifySelf = "end";
  4054. container.style.padding = "5px";
  4055. container.style.border = "2px solid rgb(100, 0, 0";
  4056. container.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
  4057. container.style.backdropFilter = "blur(5px)";
  4058. let button = document.createElement("button");
  4059. button.textContent = "Character Quick Switcher";
  4060. button.id = "silverScriptsQuickSwitcherButton";
  4061. button.style.height = "max-content";
  4062. button.addEventListener("click", function() {
  4063. document.getElementById("silverScriptsQuickSwitcherRowsContainer").style.display = "grid";
  4064. document.getElementById("silverScriptsQuickSwitcherButton").style.display = "none";
  4065. });
  4066. container.appendChild(button);
  4067.  
  4068. let subContainer = document.createElement("div");
  4069. subContainer.id = "silverScriptsQuickSwitcherRowsContainer";
  4070. subContainer.style.display = "none";
  4071. subContainer.style.rowGap = "5px";
  4072.  
  4073. let usersContainer = document.createElement("div");
  4074. usersContainer.id = "silverScriptsQuickSwitcherUsersDisplay";
  4075. usersContainer.style.display = "grid";
  4076. usersContainer.style.rowGap = "5px";
  4077. subContainer.appendChild(usersContainer);
  4078.  
  4079. let spacing = document.createElement("button");
  4080. spacing.textContent = " ";
  4081. spacing.style.height = "max-content";
  4082. spacing.style.display = "block";
  4083. spacing.style.justifySelf = "center";
  4084. subContainer.appendChild(spacing);
  4085.  
  4086. //Add page navigation
  4087. let rowContainer = document.createElement("div");
  4088. rowContainer.style.display = "grid";
  4089. rowContainer.style.gridTemplateColumns = "auto auto";
  4090. rowContainer.style.columnGap = "10px";
  4091.  
  4092. button = document.createElement("button");
  4093. button.id = "silverScriptsQuickSwitcherButtonLeft";
  4094. button.textContent = "<<";
  4095. button.style.justifySelf = "left";
  4096. button.style.marginLeft = "40%";
  4097. button.addEventListener("click", function(e) {
  4098. changeQuickSwitcherPage(false);
  4099. });
  4100. button.disabled = true;
  4101. rowContainer.appendChild(button);
  4102.  
  4103. button = document.createElement("button");
  4104. button.id = "silverScriptsQuickSwitcherButtonRight";
  4105. button.textContent = ">>";
  4106. button.style.justifySelf = "right";
  4107. button.style.marginRight = "40%";
  4108. button.addEventListener("click", function(e) {
  4109. changeQuickSwitcherPage(true);
  4110. });
  4111. button.disabled = true;
  4112. rowContainer.appendChild(button);
  4113.  
  4114. subContainer.appendChild(rowContainer);
  4115.  
  4116. button = document.createElement("button");
  4117. button.id = "silverScriptsQuickSwitcherClose";
  4118. button.textContent = "Close";
  4119. button.style.height = "max-content";
  4120. button.style.display = "block";
  4121. button.style.justifySelf = "center";
  4122. button.addEventListener("click", function() {
  4123. document.getElementById("silverScriptsQuickSwitcherRowsContainer").style.display = "none";
  4124. document.getElementById("silverScriptsQuickSwitcherButton").style.display = "block";
  4125. });
  4126. subContainer.appendChild(button);
  4127. container.appendChild(subContainer);
  4128. topMenuContainer.appendChild(container);
  4129.  
  4130. //Fill the first page of users
  4131. changeQuickSwitcherPage(true);
  4132.  
  4133. //Fast-forward the quickswicther until the page contains the lastActiveUserID entry
  4134. goToPageInQuickSwitcherFromUserID(lastActiveUserID);
  4135.  
  4136. }
  4137.  
  4138. //Add 10 users of page pageNum to the quickSwitcher
  4139. function changeQuickSwitcherPage(isNextPage) {
  4140. let usersContainer = document.getElementById("silverScriptsQuickSwitcherUsersDisplay");
  4141. //Reset content
  4142. usersContainer.innerHTML = null;
  4143. //Sort users before displaying them
  4144. let sortedCharacterCookieDataKeys = Object.values(characterCookieData).sort((a, b) => a['characterName'].localeCompare(b['characterName']))
  4145. //Check that there are enough users to display
  4146. if (isNextPage) {
  4147. if (sortedCharacterCookieDataKeys.length < ((lastQuickSwitcherPage + 1) * 10)) {
  4148. console.log("You just attempted to turn page when you don't have enough characters!!");
  4149. return;
  4150. }
  4151. } else {
  4152. if (lastQuickSwitcherPage == 0) {
  4153. console.log("You just attempted to turn to a negative page!!");
  4154. return;
  4155. }
  4156. }
  4157. //Increment/decrement page number
  4158. if (isNextPage) {
  4159. lastQuickSwitcherPage += 1;
  4160. } else {
  4161. lastQuickSwitcherPage -= 1;
  4162. }
  4163. //Get a page of characters
  4164. let prevDisplayedChars = lastQuickSwitcherPage * 10;
  4165. let remainingChars = sortedCharacterCookieDataKeys.length - prevDisplayedChars;
  4166. let usersToDisplay = prevDisplayedChars + Math.min(prevDisplayedChars + 10, remainingChars);
  4167. sortedCharacterCookieDataKeys = sortedCharacterCookieDataKeys.slice(prevDisplayedChars,
  4168. usersToDisplay);
  4169.  
  4170. //Enable/disable buttons if needed
  4171. let leftButton = document.getElementById("silverScriptsQuickSwitcherButtonLeft");
  4172. let rightButton = document.getElementById("silverScriptsQuickSwitcherButtonRight");
  4173. if (lastQuickSwitcherPage > 0) {
  4174. leftButton.disabled = false;
  4175. } else {
  4176. leftButton.disabled = true;
  4177. }
  4178. if (remainingChars > 10) {
  4179. rightButton.disabled = false;
  4180. } else {
  4181. rightButton.disabled = true;
  4182. }
  4183.  
  4184. for (let user in sortedCharacterCookieDataKeys) {
  4185. let rowContainer = document.createElement("div");
  4186. rowContainer.style.display = "grid";
  4187. rowContainer.style.gridTemplateColumns = "auto max-content";
  4188. rowContainer.style.columnGap = "10px";
  4189.  
  4190. let button = document.createElement("button");
  4191. button.dataset.userId = sortedCharacterCookieDataKeys[user]["userID"];
  4192. button.textContent = sortedCharacterCookieDataKeys[user]["characterName"];
  4193. button.style.height = "100%";
  4194. button.style.minWidth = "50px";
  4195. button.style.display = "block";
  4196. button.style.justifySelf = "left";
  4197. button.addEventListener("click", function(e) {
  4198. let userID = e.target.dataset.userId;
  4199. if (e.shiftKey) {
  4200. changeCharacterCookieDataName(userID);
  4201. //Reset the menu to refresh the view, reopening the saved users
  4202. document.getElementById("silverScriptsQuickSwitcherCluster").remove();
  4203. addQuickSwitcherButton();
  4204. document.getElementById("silverScriptsQuickSwitcherRowsContainer").style.display = "grid";
  4205. document.getElementById("silverScriptsQuickSwitcherButton").style.display = "none";
  4206. } else {
  4207. changeCharacter(characterCookieData[userID]["cookie"]);
  4208. }
  4209. });
  4210. if (sortedCharacterCookieDataKeys[user]["userID"] == lastActiveUserID) {
  4211. button.disabled = true;
  4212. }
  4213. rowContainer.appendChild(button);
  4214.  
  4215. button = document.createElement("button");
  4216. button.dataset.userId = sortedCharacterCookieDataKeys[user]["userID"];
  4217. button.textContent = "X";
  4218. button.style.height = "max-content";
  4219. button.style.display = "block";
  4220. button.style.justifySelf = "right";
  4221. button.addEventListener("click", function(e) {
  4222. //Remove saved character's cookies
  4223. let userID = e.target.dataset.userId;
  4224. removeCharacterFromCharacterCookieData(userID);
  4225. //Reset the menu to refresh the view, reopening the saved users
  4226. document.getElementById("silverScriptsQuickSwitcherCluster").remove();
  4227. addQuickSwitcherButton();
  4228. document.getElementById("silverScriptsQuickSwitcherRowsContainer").style.display = "grid";
  4229. document.getElementById("silverScriptsQuickSwitcherButton").style.display = "none";
  4230. });
  4231. rowContainer.appendChild(button);
  4232.  
  4233. usersContainer.appendChild(rowContainer);
  4234. }
  4235. }
  4236.  
  4237. function goToPageInQuickSwitcherFromUserID(userID) {
  4238. //Sort as usual
  4239. let sortedCharacterCookieDataKeys = Object.values(characterCookieData).sort((a, b) => a['characterName'].localeCompare(b['characterName']));
  4240. //Find the index of the active user
  4241. let userIndex = sortedCharacterCookieDataKeys.findIndex(user => user.userID === userID);
  4242. //Find the page belonging to the specified user. Check if > -1,
  4243. //otherwise it means that the serach failed
  4244. if (userIndex > -1) {
  4245. let pageNumberContainingUser = Math.floor(userIndex / 10);
  4246. //Flip pages forwards or backwards until equal
  4247. while (lastQuickSwitcherPage < pageNumberContainingUser) {
  4248. changeQuickSwitcherPage(true);
  4249. }
  4250. while (lastQuickSwitcherPage > pageNumberContainingUser) {
  4251. changeQuickSwitcherPage(false);
  4252. }
  4253. }
  4254. }
  4255.  
  4256. //Add global IC navigation button
  4257. function addInnerCityButton() {
  4258. //Be sure to not add button if already in IC
  4259. if (isAtLocation("innerCity") || isAtLocation("ICInventory") || !userSettings.innerCityButton) {
  4260. return;
  4261. }
  4262.  
  4263. let topMenuContainer = document.getElementById("silverScriptsTopMenuContainer");
  4264. let container = document.createElement("div");
  4265. container.style.height = "max-content";
  4266. container.style.width = "max-content";
  4267. container.style.minWidth = "41px";
  4268. container.style.justifySelf = "end";
  4269. container.style.padding = "5px";
  4270. container.style.border = "2px solid rgb(100, 0, 0";
  4271. container.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
  4272. container.style.backdropFilter = "blur(5px)";
  4273. let button = document.createElement("button");
  4274. button.textContent = "Go to Inner City";
  4275. button.id = "silverScriptsInnerCityButton";
  4276. button.style.height = "max-content";
  4277. button.addEventListener("click", function() {
  4278. unsafeWindow.playSound("outpost");
  4279. setTimeout(function() {
  4280. unsafeWindow.doPageChange(21, 1);
  4281. }, 1000);
  4282. });
  4283. container.appendChild(button);
  4284. topMenuContainer.appendChild(container);
  4285. }
  4286.  
  4287. function fillHoverBox() {
  4288. //Don't do anything if hoverPrices got disabled
  4289. if (!userSettings.hoverPrices) {
  4290. infoBox.style.opacity = 1;
  4291. return;
  4292. }
  4293.  
  4294. var targetInventoryItem = inventoryArray[lastSlotHovered - 1];
  4295.  
  4296. //Don't do anything if slot is empty
  4297. if (targetInventoryItem == null || targetInventoryItem.id == "") {
  4298. return;
  4299. }
  4300.  
  4301. //Don't do anything is item is non-tradeable
  4302. if (targetInventoryItem.notTransferable) {
  4303. infoBox.style.opacity = 1;
  4304. return;
  4305. }
  4306.  
  4307. //Remove previous script info
  4308. var previousInfo = document.getElementsByClassName("silverStats");
  4309. for (var i = previousInfo.length - 1; i >= 0; i--) {
  4310. previousInfo[i].parentNode.removeChild(previousInfo[i]);
  4311. }
  4312.  
  4313. var blank = document.createElement("div");
  4314. blank.className = "itemData silverStats";
  4315. blank.innerHTML = "Silver Stats";
  4316. blank.style.opacity = 0;
  4317. infoBox.appendChild(blank);
  4318.  
  4319. if (targetInventoryItem.bestPricePerUnit != undefined) {
  4320. var bpu = document.createElement("div");
  4321. bpu.className = "itemData silverStats";
  4322. bpu.innerHTML = "Best price per unit: " + targetInventoryItem.bestPricePerUnit.toFixed(2);
  4323. infoBox.appendChild(bpu);
  4324.  
  4325. //Save a text line if item quantity 1
  4326. if (targetInventoryItem.quantity != 1 && targetInventoryItem.type != "Armour") {
  4327. var bps = document.createElement("div");
  4328. bps.className = "itemData silverStats";
  4329. bps.innerHTML = "Best price this stack: " + (targetInventoryItem.bestPricePerUnit * targetInventoryItem.quantity).toFixed(2);
  4330. infoBox.appendChild(bps);
  4331. }
  4332. }
  4333.  
  4334. if (targetInventoryItem.averagePricePerUnit != undefined) {
  4335. var apu = document.createElement("div");
  4336. apu.className = "itemData silverStats";
  4337. apu.innerHTML = "Average price per unit: " + targetInventoryItem.averagePricePerUnit.toFixed(2);
  4338. infoBox.appendChild(apu);
  4339.  
  4340. //Save a text if item quantity 1
  4341. if (targetInventoryItem.quantity != 1 && targetInventoryItem.type != "Armour") {
  4342. var aps = document.createElement("div");
  4343. aps.className = "itemData silverStats";
  4344. aps.innerHTML = "Average price this stack: " + (targetInventoryItem.averagePricePerUnit * targetInventoryItem.quantity).toFixed(2);
  4345. infoBox.appendChild(aps);
  4346. }
  4347. }
  4348.  
  4349. //Check if a service is needed to use the item
  4350. var serv = document.createElement("div");
  4351. serv.className = "itemData silverStats";
  4352. if (targetInventoryItem.type == "Cookable") {
  4353. if (servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel] != undefined) {
  4354. serv.innerHTML = "Price to cook: " + (servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel][0]["price"]);
  4355. } else {
  4356. serv.innerHTML = "No services available at this outpost";
  4357. }
  4358. infoBox.appendChild(serv);
  4359. //If an item is cookable, insert info about its cooked counterpart
  4360. var cookedItemId = targetInventoryItem.id + "_cooked";
  4361. var cookedItem = itemsDataBank[cookedItemId];
  4362. if (cookedItem != null && cookedItem.bestPricePerUnit != null && cookedItem.averagePricePerUnit != null && servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel] != undefined) {
  4363. var cookedBestPrice = document.createElement("div");
  4364. cookedBestPrice.className = "itemData silverStats";
  4365. cookedBestPrice.innerHTML = "Best price cooked: " + cookedItem.bestPricePerUnit.toFixed(2);
  4366. infoBox.appendChild(cookedBestPrice);
  4367. var cookedAveragePrice = document.createElement("div");
  4368. cookedAveragePrice.className = "itemData silverStats";
  4369. cookedAveragePrice.innerHTML = "Average price cooked: " + cookedItem.averagePricePerUnit.toFixed(2);
  4370. infoBox.appendChild(cookedAveragePrice);
  4371. var lowestCookingEarnings = document.createElement("div");
  4372. lowestCookingEarnings.className = "itemData silverStats";
  4373. lowestCookingEarnings.innerHTML = "Lowest earnings after cooking: " + (cookedItem.bestPricePerUnit - targetInventoryItem.bestPricePerUnit - servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel][0]["price"]).toFixed(2);
  4374. infoBox.appendChild(lowestCookingEarnings);
  4375. }
  4376. } else if (targetInventoryItem.type == "Armour") {
  4377. if (servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel] != undefined) {
  4378. serv.innerHTML = "Price to repair: " + (servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel][0]["price"]);
  4379. } else {
  4380. serv.innerHTML = "No services available at this outpost";
  4381. }
  4382. infoBox.appendChild(serv);
  4383. } else if (targetInventoryItem.type == "Medical") {
  4384. if (servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel] != undefined) {
  4385. serv.innerHTML = "Price to administer: " + (servicesDataBank[targetInventoryItem.profession][targetInventoryItem.professionLevel][0]["price"]);
  4386. } else {
  4387. serv.innerHTML = "No services available at this outpost";
  4388. }
  4389. infoBox.appendChild(serv);
  4390. }
  4391.  
  4392. //Show an armor, weapon, or cosmetic scrap value
  4393. if (targetInventoryItem.scrapValue != undefined) {
  4394. var scrapValue = document.createElement("div");
  4395. scrapValue.className = "itemData silverStats";
  4396. scrapValue.innerHTML = "Scrap value: " + targetInventoryItem.scrapValue;
  4397. infoBox.appendChild(scrapValue);
  4398. }
  4399.  
  4400. //Make hoverBox visible when all its content is updated
  4401. setTimeout(function() {
  4402. infoBox.style.opacity = 1;
  4403. }, 10);
  4404. }
  4405.  
  4406. function createAndAppendHelpWindowElement(parentNode, elementData) {
  4407. var elementType = elementData[0];
  4408. var elementText = elementData[1];
  4409. var newElement = document.createElement(elementType);
  4410. newElement.innerHTML = elementText;
  4411.  
  4412. for (var elementAttributeName in elementData[2]) {
  4413. if (elementAttributeName == "style" || elementAttributeName == "dataset") {
  4414. for (var elementListData of elementData[2][elementAttributeName]) {
  4415. var dataKey = elementListData[0];
  4416. var dataValue = elementListData[1];
  4417. newElement[elementAttributeName][dataKey] = dataValue;
  4418. }
  4419. } else {
  4420. newElement[elementAttributeName] = elementData[2][elementAttributeName];
  4421. }
  4422. }
  4423.  
  4424. if (elementType == "button") {
  4425. var buttonFunction = elementData[3];
  4426. var buttonFunctionArgs = elementData[4];
  4427. newElement.addEventListener("click", buttonFunction.bind(null, ...buttonFunctionArgs));
  4428. } else if (elementType == "div") {
  4429. for (var divElement of elementData[3]) {
  4430. createAndAppendHelpWindowElement(newElement, divElement);
  4431. }
  4432. }
  4433.  
  4434. parentNode.appendChild(newElement);
  4435. }
  4436.  
  4437. function openHelpWindowPage(pageName) {
  4438.  
  4439. unsafeWindow.pageLock = true;
  4440. helpWindow.innerHTML = "";
  4441.  
  4442. //Reset helpWindow to default style
  4443. helpWindow.style.position = "absolute";
  4444. helpWindow.style.left = "208px";
  4445. helpWindow.style.top = "195px";
  4446. helpWindow.style.width = "270px";
  4447. helpWindow.style.height = "100px";
  4448.  
  4449. for (var windowStyleData of helpWindowStructure[pageName]["style"]) {
  4450. var styleCategory = windowStyleData[0];
  4451. var styleValue = windowStyleData[1];
  4452. helpWindow.style[styleCategory] = styleValue;
  4453. }
  4454.  
  4455. for (var windowElementData of helpWindowStructure[pageName]["data"]) {
  4456. createAndAppendHelpWindowElement(helpWindow, windowElementData);
  4457. var breakline = document.createElement("br");
  4458. helpWindow.appendChild(breakline);
  4459. }
  4460.  
  4461. helpWindow.parentNode.style.display = "block";
  4462. helpWindow.focus();
  4463.  
  4464. }
  4465.  
  4466. function closeHelpWindowPage() {
  4467. //Reset helpWindow to default style
  4468. helpWindow.style.position = "absolute";
  4469. helpWindow.style.left = "208px";
  4470. helpWindow.style.top = "195px";
  4471. helpWindow.style.width = "270px";
  4472. helpWindow.style.height = "100px";
  4473.  
  4474. helpWindow.parentNode.style.display = "none";
  4475. helpWindow.innerHTML = "";
  4476. helpWindow.style.height = "";
  4477. unsafeWindow.pageLock = false;
  4478. }
  4479.  
  4480. function injectAutoMarketWithdrawButton(marketRow) {
  4481. //We must check if the player doesn't have enough money
  4482. //Remove dollar sign and commas
  4483. var rawPrice = marketRow.getElementsByClassName("salePrice")[0].innerHTML;
  4484. var itemPrice = rawPrice == "Free" ? 0 : parseInt(rawPrice.replace(/\$/g, '').replace(/,/g, ''));
  4485. if (itemPrice <= userVars["DFSTATS_df_cash"]) {
  4486. return;
  4487. }
  4488. //We use the clone node trick to remove the click listeners
  4489. var buyButton = marketRow.getElementsByTagName('button')[0];
  4490. var withdrawButton = buyButton.cloneNode(true);
  4491. marketRow.replaceChild(withdrawButton, buyButton);
  4492. withdrawButton.innerHTML = "withdraw";
  4493. withdrawButton.style.left = "576px";
  4494. //We must check if the player has enough banked money
  4495. if (userVars['DFSTATS_df_bankcash'] > itemPrice) {
  4496. withdrawButton.disabled = false;
  4497. } else {
  4498. withdrawButton.disabled = true;
  4499. }
  4500. withdrawButton.addEventListener("click", withdrawCash.bind(null, itemPrice));
  4501. }
  4502.  
  4503. ////////////////////
  4504. // Script Start //
  4505. ///////////////////
  4506.  
  4507. async function startScript() {
  4508.  
  4509.  
  4510. initUserData();
  4511. await loadStoredSettings();
  4512. await loadSavedMarketData();
  4513. await loadStoredCharacterCookieData();
  4514. await loadLastActiveUserID();
  4515. initLoadouts();
  4516. initInventoryArray();
  4517. addAllAmmoToDatabank();
  4518. addLevelAppropriateMedicalToDatabank();
  4519. addLevelAppropriateFoodToDatabank();
  4520. refreshServicesDataBank();
  4521. registerEventListeners();
  4522. registerDOMObservers();
  4523. removeHoverBoxPointerEvents();
  4524. addTopMenuContainer();
  4525. addHelpButton();
  4526. addLoadoutButton();
  4527. addQuickSwitcherButton();
  4528. addInnerCityButton();
  4529. addBoostTimers();
  4530. moveTooltipAndGrabbedItemOnTop();
  4531.  
  4532. addKonami();
  4533.  
  4534. checkForScriptUpdate();
  4535.  
  4536. }
  4537.  
  4538. //Export the scripts presence to the unsafeWindow as soon as possible in order to notify
  4539. //other scripts if necessary
  4540. unsafeWindow.silverScriptsPresence = true;
  4541.  
  4542. //Give enough time to the vanilla js to complete initialisation.
  4543. setTimeout(async function() {
  4544. await startScript();
  4545. }, 500);
  4546.  
  4547. })();