IdlePixel Market Overhaul

Overhaul of market UI and functionality.

  1. // ==UserScript==
  2. // @name IdlePixel Market Overhaul
  3. // @namespace com.anwinity.idlepixel
  4. // @version 1.0.17
  5. // @description Overhaul of market UI and functionality.
  6. // @author Anwinity
  7. // @license MIT
  8. // @match *://idle-pixel.com/login/play*
  9. // @grant none
  10. // @require https://gf.qytechs.cn/scripts/441206-idlepixel/code/IdlePixel+.js?anticache=20220905
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const XP_PER = {
  17. stone: 0.1,
  18. copper: 1,
  19. iron: 5,
  20. silver: 10,
  21. gold: 20,
  22. promethium: 100,
  23. titanium: 300,
  24.  
  25. bronze_bar: 5,
  26. iron_bar: 25,
  27. silver_bar: 50,
  28. gold_bar: 100,
  29. promethium_bar: 500,
  30. titanium_bar: 2000
  31. };
  32.  
  33. const BONEMEAL_PER = {
  34. bones: 1,
  35. big_bones: 2,
  36. ice_bones: 3,
  37. ashes: 2
  38. };
  39.  
  40. class MarketPlugin extends IdlePixelPlusPlugin {
  41. constructor() {
  42. super("market", {
  43. about: {
  44. name: GM_info.script.name,
  45. version: GM_info.script.version,
  46. author: GM_info.script.author,
  47. description: GM_info.script.description
  48. },
  49. config: [
  50. {
  51. id: "condensed",
  52. label: "Condensed UI",
  53. type: "boolean",
  54. default: true
  55. },
  56. {
  57. id: "sortMethod",
  58. label: "Sort Method",
  59. type: "select",
  60. default: "default",
  61. options: [
  62. {value: "default", label: "Default"},
  63. {value: "timeDESC", label: "Time (Newest First)"},
  64. {value: "timeASC", label: "Time (Newest Last)"},
  65. {value: "priceASC", label: "Price (Cheapest First)"},
  66. {value: "priceDESC", label: "Price (Cheapest Last)"},
  67. ]
  68. },
  69. {
  70. id: "highlightBest",
  71. label: "Highlight Best",
  72. type: "boolean",
  73. default: true
  74. },
  75. {
  76. id: "autoMax",
  77. label: "Autofill Max Buy",
  78. type: "boolean",
  79. default: false
  80. },
  81. ]
  82. });
  83. this.lastBrowsedItem = "all";
  84. this.lastCategoryFilter = "all";
  85. }
  86.  
  87. onConfigsChanged() {
  88. this.applyCondensed(this.getConfig("condensed"));
  89. }
  90.  
  91. applyCondensed(condensed) {
  92. if(condensed) {
  93. $("#panel-player-market").addClass("condensed");
  94. $("#modal-market-select-item").addClass("condensed");
  95. }
  96. else {
  97. $("#panel-player-market").removeClass("condensed");
  98. $("#modal-market-select-item").removeClass("condensed");
  99. }
  100. }
  101.  
  102. onLogin() {
  103. const self = this;
  104.  
  105. $("head").append(`
  106. <style id="styles-market">
  107.  
  108. #market-table {
  109. margin-top: 0.5em !important;
  110. }
  111. #market-table tr.cheaper {
  112. background-color: rgb(50, 205, 50, 0.25);
  113. }
  114. #market-table tr.cheaper > td {
  115. background-color: rgb(50, 205, 50, 0.25);
  116. }
  117. #panel-player-market.condensed > center {
  118. display: flex;
  119. flex-direction: row;
  120. justify-content: center;
  121. }
  122. #panel-player-market.condensed div.player-market-slot-base {
  123. height: 400px;
  124. }
  125. #panel-player-market.condensed div.player-market-slot-base hr {
  126. margin-top: 2px;
  127. margin-bottom: 4px;
  128. }
  129. #panel-player-market.condensed div.player-market-slot-base br + #panel-player-market.condensed div.player-market-slot-base br {
  130. display: none;
  131. }
  132. #panel-player-market.condensed div.player-market-slot-base[id^="player-market-slot-occupied"] button {
  133. padding: 2px;
  134. }
  135.  
  136. #panel-player-market.condensed #market-table th {
  137. padding: 2px 4px;
  138. }
  139.  
  140. #panel-player-market.condensed #market-table td {
  141. padding: 2px 4px;
  142. }
  143.  
  144. #modal-market-select-item.condensed #modal-market-select-item-section .select-item-tradables-catagory {
  145. margin: 6px 6px;
  146. padding: 6px 6px;
  147. }
  148.  
  149. #modal-market-select-item.condensed #modal-market-select-item-section .select-item-tradables-catagory hr {
  150. margin-top: 2px;
  151. margin-bottom: 2px;
  152. }
  153. #market-category-filters {
  154. display: flex;
  155. flex-direction: row;
  156. justify-content: center;
  157. align-items: start;
  158. flex-wrap: wrap;
  159. margin: 0.25em;
  160. }
  161. #market-category-filters > button {
  162. display: inline-block;
  163. margin: 0.25em;
  164. }
  165. #market-category-filters > button.active {
  166. background-color: #74DBDB;
  167. }
  168.  
  169. </style>
  170. `);
  171. // modal-market-configure-item-to-sell-amount
  172. const sellModal = $("#modal-market-configure-item-to-sell");
  173. const sellAmountInput = sellModal.find("#modal-market-configure-item-to-sell-amount");
  174. sellAmountInput.after(`
  175. <button type="button" onclick="IdlePixelPlus.plugins.market.applyOneAmountSell()">1</button>
  176. <button type="button" onclick="IdlePixelPlus.plugins.market.applyMaxAmountSell()">max</button>
  177. <button type="button" onclick="IdlePixelPlus.plugins.market.applyMaxAmountSell(true)">max-1</button>
  178. `);
  179. const sellPriceInput = sellModal.find("#modal-market-configure-item-to-sell-price-each").after(`
  180. <button type="button" onclick="IdlePixelPlus.plugins.market.applyMinPriceSell()">min</button>
  181. <button type="button" onclick="IdlePixelPlus.plugins.market.applyMidPriceSell()">mid</button>
  182. <button type="button" onclick="IdlePixelPlus.plugins.market.applyMaxPriceSell()">max</button>
  183. <br /><br />
  184. Total: <span id="modal-market-configure-item-to-sell-total"></span>
  185. `);
  186.  
  187. sellAmountInput.on("input change", () => this.applyTotalSell());
  188. sellPriceInput.on("input change", () => this.applyTotalSell());
  189.  
  190. const buyModal = $("#modal-market-purchase-item");
  191. const buyAmountInput = buyModal.find("#modal-market-purchase-item-amount-input");
  192. buyAmountInput.after(`
  193. <button type="button" onclick="IdlePixelPlus.plugins.market.applyOneAmountBuy()">1</button>
  194. <button type="button" onclick="IdlePixelPlus.plugins.market.applyMaxAmountBuy()">max</button>
  195. <br /><br />
  196. Total: <span id="modal-market-purchase-item-total"></span>
  197. <br />
  198. Owned: <item-display data-format="number" data-key="coins"></item-display>
  199. `);
  200. buyAmountInput.on("input change", () => this.applyTotalBuy());
  201.  
  202. // wrap Market.browse_get_table to capture last selected
  203. const original_market_browse = Market.browse_get_table;
  204. Market.browse_get_table = function(item) {
  205. return self.browseGetTable(item)
  206. .always(() => {
  207. self.filterTable();
  208. });
  209. }
  210.  
  211. $("#market-table").css("margin-top", "24px");
  212. $("#market-table").parent().before(`<div id="market-category-filters"><div>`);
  213.  
  214. // wrap Market.load_tradables to populate category filters
  215. const original_load_tradables = Market.load_tradables;
  216. Market.load_tradables = function(data) {
  217. original_load_tradables.apply(this, arguments);
  218. self.createFilterButtons();
  219. }
  220. self.createFilterButtons();
  221.  
  222. $(`#panel-player-market button[onclick^="Market.clicks_browse_player_market_button"]`)
  223. .first()
  224. .after(`<button id="refresh-market-table-button" type="button" style="margin-left: 0.5em" onclick="IdlePixelPlus.plugins.market.refreshMarket(true);">Refresh</button>`);
  225.  
  226. //$("button.market-remove-button").after('<br><br><br><button onclick="" class="market-rebrowse-button">Browse</button>');
  227.  
  228. this.onConfigsChanged();
  229. }
  230.  
  231. browseGetTable(item) {
  232. //console.log(`browseGetTable("${item}")`);
  233. const self = this;
  234. this.lastBrowsedItem = item;
  235. if(item != "all") {
  236. self.lastCategoryFilter = "all";
  237. }
  238. if(item == "all") {
  239. $("#market-category-filters").show();
  240. }
  241. else {
  242. $("#market-category-filters").hide();
  243. }
  244.  
  245. // A good chunk of this is taking directly from Market.browse_get_table
  246. hide_element("market-table");
  247. show_element("market-loading");
  248. let best = {};
  249. let bestList = {};
  250. return $.get(`../../market/browse/${item}/`).done(function(data) {
  251. const xpMultiplier = DonorShop.has_donor_active(IdlePixelPlus.getVar("donor_bonus_xp_timestamp")) ? 1.1 : 1;
  252. //console.log(data);
  253. data.forEach(datum => {
  254. const priceAfterTax = datum.market_item_price_each * 1.01;
  255. switch(datum.market_item_category) {
  256. case "bars":
  257. case "ores": {
  258. let perCoin = (priceAfterTax / (xpMultiplier*XP_PER[datum.market_item_name]));
  259. datum.perCoin = perCoin;
  260. datum.perCoinLabel = `${perCoin.toFixed(perCoin < 10 ? 2 : 1)} coins/xp`;
  261. if(!best[datum.market_item_category]) {
  262. best[datum.market_item_category] = perCoin;
  263. bestList[datum.market_item_category] = [datum];
  264. }
  265. else {
  266. if(perCoin == best[datum.market_item_category]) {
  267. bestList[datum.market_item_category].push(datum);
  268. }
  269. else if(perCoin < best[datum.market_item_category]) {
  270. bestList[datum.market_item_category] = [datum];
  271. best[datum.market_item_category] = perCoin;
  272. }
  273. }
  274. break;
  275. }
  276. case "logs": {
  277. let perCoin = (priceAfterTax / Cooking.getHeatPerLog(datum.market_item_name));
  278. datum.perCoin = perCoin;
  279. datum.perCoinLabel = `${perCoin.toFixed(perCoin < 10 ? 2 : 1)} coins/heat`;
  280. if(!best[datum.market_item_category]) {
  281. best[datum.market_item_category] = perCoin;
  282. bestList[datum.market_item_category] = [datum];
  283. }
  284. else {
  285. if(perCoin == best[datum.market_item_category]) {
  286. bestList[datum.market_item_category].push(datum);
  287. }
  288. else if(perCoin < best[datum.market_item_category]) {
  289. bestList[datum.market_item_category] = [datum];
  290. best[datum.market_item_category] = perCoin;
  291. }
  292. }
  293. break;
  294. }
  295. case "raw_fish":
  296. case "cooked_fish":{
  297. let perCoin = (priceAfterTax / Cooking.get_energy(datum.market_item_name));
  298. datum.perCoin = perCoin;
  299. datum.perCoinLabel = `${perCoin.toFixed(perCoin < 10 ? 2 : 1)} coins/energy`;
  300. if(!best[datum.market_item_category]) {
  301. best[datum.market_item_category] = perCoin;
  302. bestList[datum.market_item_category] = [datum];
  303. }
  304. else {
  305. if(perCoin == best[datum.market_item_category]) {
  306. bestList[datum.market_item_category].push(datum);
  307. }
  308. else if(perCoin < best[datum.market_item_category]) {
  309. bestList[datum.market_item_category] = [datum];
  310. best[datum.market_item_category] = perCoin;
  311. }
  312. }
  313. break;
  314. }
  315. case "bones": {
  316. let perCoin = (priceAfterTax / BONEMEAL_PER[datum.market_item_name]);
  317. datum.perCoin = perCoin;
  318. datum.perCoinLabel = `${perCoin.toFixed(perCoin < 10 ? 2 : 1)} coins/bonemeal`;
  319. if(!best[datum.market_item_category]) {
  320. best[datum.market_item_category] = perCoin;
  321. bestList[datum.market_item_category] = [datum];
  322. }
  323. else {
  324. if(perCoin == best[datum.market_item_category]) {
  325. bestList[datum.market_item_category].push(datum);
  326. }
  327. else if(perCoin < best[datum.market_item_category]) {
  328. bestList[datum.market_item_category] = [datum];
  329. best[datum.market_item_category] = perCoin;
  330. }
  331. }
  332. break;
  333. }
  334. default: {
  335. datum.perCoin = Number.MAX_SAFE_INTEGER;
  336. datum.perCoinLabel = "";
  337. break;
  338. }
  339. }
  340. });
  341. Object.values(bestList).forEach(bestCatList => bestCatList.forEach(datum => datum.best=true));
  342. const sortMethod = self.getConfig("sortMethod");
  343. switch(sortMethod) {
  344. case "timeDESC": {
  345. data = data.sort((a, b) => b.market_item_post_timestamp - a.market_item_post_timestamp);
  346. break;
  347. }
  348. case "timeASC": {
  349. data = data.sort((a, b) => a.market_item_post_timestamp - b.market_item_post_timestamp);
  350. break;
  351. }
  352. case "priceASC": {
  353. data = data.sort((a, b) => {
  354. if(a.perCoin != b.perCoin && typeof a.perCoin==="number" && typeof b.perCoin==="number") {
  355. return a.perCoin - b.perCoin;
  356. }
  357. return a.market_item_price_each - b.market_item_price_each;
  358. });
  359.  
  360. // DEBUG
  361. //data.filter(x => x.market_item_category == "cooked_fish").forEach(d => {
  362. // console.log(`${d.market_item_name} ${d.perCoin} ${d.market_item_price_each}`);
  363. //});
  364. //
  365.  
  366. break;
  367. }
  368. case "priceDESC": {
  369. data = data.sort((a, b) => {
  370. if(a.perCoin != b.perCoin && typeof a.perCoin==="number" && typeof b.perCoin==="number") {
  371. return b.perCoin - a.perCoin;
  372. }
  373. return b.market_item_price_each - a.market_item_price_each;
  374. });
  375. break;
  376. }
  377. }
  378. // console.log(data);
  379. let html = "<tr><th>ITEM</th><th></th><th>AMOUNT</th><th>PRICE EACH</th><th>CATEGORY</th><th>EXPIRES IN</th></tr>";
  380. // in case you want to add any extra data to the table but still use this script
  381. if(typeof window.ModifyMarketDataHeader === "function") {
  382. html = window.ModifyMarketDataHeader(html);
  383. }
  384. data.forEach(datum => {
  385. let market_id = datum.market_id;
  386. let player_id = datum.player_id;
  387. let item_name = datum.market_item_name;
  388. let amount = datum.market_item_amount;
  389. let price_each = datum.market_item_price_each;
  390. let category = datum.market_item_category;
  391. let timestamp = datum.market_item_post_timestamp;
  392. let perCoinLabel = datum.perCoinLabel;
  393. let best = datum.best && self.getConfig("highlightBest");
  394.  
  395. let your_entry = "";
  396. if(Items.getItem("player_id") == player_id) {
  397. your_entry = "<span class='color-grey font-small'><br /><br />(Your Item)</span>";
  398. }
  399.  
  400. let rowHtml = "";
  401. rowHtml += `<tr onclick="Modals.market_purchase_item('${market_id}', '${item_name}', '${amount}', '${price_each}'); IdlePixelPlus.plugins.market.applyMaxAmountBuyIfConfigured();" class="hover${ best ? ' cheaper' : '' }">`;
  402. rowHtml += `<td>${Items.get_pretty_item_name(item_name)}${your_entry}</td>`;
  403. rowHtml += `<td><img src="https://d1xsc8x7nc5q8t.cloudfront.net/images/${item_name}.png" /></td>`;
  404. rowHtml += `<td>${amount}</td>`;
  405. rowHtml += `<td><img src="https://d1xsc8x7nc5q8t.cloudfront.net/images/coins.png" /> ${Market.get_price_after_tax(price_each)}`;
  406. if(perCoinLabel) {
  407. rowHtml += `<br /><span style="font-size: 80%; opacity: 0.8">${perCoinLabel}</span>`;
  408. }
  409. rowHtml += `</td>`;
  410. rowHtml += `<td>${category}</td>`;
  411. rowHtml += `<td>${Market._get_expire_time(timestamp)}</td>`;
  412. rowHtml += `</tr>`;
  413.  
  414. // in case you want to add any extra data to the table but still use this script
  415. if(typeof window.ModifyMarketDataRow === "function") {
  416. rowHtml = window.ModifyMarketDataRow(datum, rowHtml);
  417. }
  418. html += rowHtml;
  419. });
  420.  
  421. document.getElementById("market-table").innerHTML = html;
  422. hide_element("market-loading");
  423. show_element("market-table");
  424. });
  425.  
  426. }
  427.  
  428. createFilterButtons() {
  429. const filters = $("#market-category-filters");
  430. filters.empty();
  431. if(Market.tradables) {
  432. const categories = [];
  433. Market.tradables.forEach(tradable => {
  434. if(!categories.includes(tradable.category)) {
  435. categories.push(tradable.category);
  436. }
  437. });
  438. filters.append(`<button data-category="all" onclick="IdlePixelPlus.plugins.market.filterTable('all')">All</button>`);
  439. categories.forEach(cat => {
  440. filters.append(`<button data-category="${cat}" onclick="IdlePixelPlus.plugins.market.filterTable('${cat}')">${cat.replace(/_/g, " ").replace(/(^|\s)\w/g, s => s.toUpperCase())}</button>`);
  441. });
  442. }
  443. }
  444.  
  445. filterTable(category) {
  446.  
  447. if(category) {
  448. this.lastCategoryFilter = category;
  449. }
  450. else {
  451. category = this.lastCategoryFilter || "all";
  452. }
  453.  
  454. $("#market-category-filters button.active").removeClass("active");
  455. $(`#market-category-filters button[data-category="${category}"]`).addClass("active");
  456.  
  457. const rows = $("#market-table tbody tr.hover");
  458. if(category=="all") {
  459. rows.show();
  460. }
  461. else {
  462. rows.each(function() {
  463. const row = $(this);
  464. const rowCategory = row.find("td:nth-child(5)").text();
  465. if(category == rowCategory) {
  466. row.show();
  467. }
  468. else {
  469. row.hide();
  470. }
  471. });
  472.  
  473. }
  474. }
  475.  
  476. refreshMarket(disableButtonForABit) {
  477. if(this.lastBrowsedItem) {
  478. Market.browse_get_table(this.lastBrowsedItem);
  479. if(disableButtonForABit) { // prevent spam clicking it
  480. $("#refresh-market-table-button").prop("disabled", true);
  481. setTimeout(() => {
  482. $("#refresh-market-table-button").prop("disabled", false);
  483. }, 700);
  484. }
  485. }
  486. }
  487.  
  488. applyOneAmountBuy() {
  489. $("#modal-market-purchase-item #modal-market-purchase-item-amount-input").val(1);
  490. this.applyTotalBuy();
  491. }
  492.  
  493. applyMaxAmountBuyIfConfigured() {
  494. if(this.getConfig("autoMax")) {
  495. this.applyMaxAmountBuy();
  496. }
  497. }
  498.  
  499. applyMaxAmountBuy(minus1=false) {
  500. const coinsOwned = IdlePixelPlus.getVarOrDefault("coins", 0, "int");
  501. const price = parseInt($("#modal-market-purchase-item #modal-market-purchase-item-price-each").val().replace(/[^\d]+/g, ""));
  502. const maxAffordable = Math.floor(coinsOwned / price);
  503. const maxAvailable = parseInt($("#modal-market-purchase-item #modal-market-purchase-item-amount-left").val().replace(/[^\d]+/g, ""));
  504. let max = Math.min(maxAffordable, maxAvailable);
  505. if(minus1) {
  506. max--;
  507. }
  508. if(max < 0) {
  509. max = 0;
  510. }
  511. $("#modal-market-purchase-item #modal-market-purchase-item-amount-input").val(max);
  512. this.applyTotalBuy();
  513. }
  514.  
  515. parseIntKMBT(s) {
  516. if(typeof s === "number") {
  517. return Math.floor(s);
  518. }
  519. s = s.toUpperCase().replace(/[^\dKMBT]+/, "");
  520. if(s.endsWith("K")) {
  521. s = s.replace(/K$/, "000");
  522. }
  523. else if(s.endsWith("M")) {
  524. s = s.replace(/M$/, "000000");
  525. }
  526. else if(s.endsWith("B")) {
  527. s = s.replace(/B$/, "000000000");
  528. }
  529. else if(s.endsWith("T")) {
  530. s = s.replace(/T$/, "000000000000");
  531. }
  532. return parseInt(s);
  533. }
  534.  
  535. applyTotalBuy() {
  536. const amount = this.parseIntKMBT($("#modal-market-purchase-item #modal-market-purchase-item-amount-input").val());
  537. const price = this.parseIntKMBT($("#modal-market-purchase-item #modal-market-purchase-item-price-each").val().replace("Price each: ", ""));
  538. const total = amount*price;
  539. const totalElement = $("#modal-market-purchase-item-total");
  540. if(isNaN(total)) {
  541. totalElement.text("");
  542. }
  543. else {
  544. totalElement.text(total.toLocaleString());
  545. const coinsOwned = IdlePixelPlus.getVarOrDefault("coins", 0, "int");
  546. if(total > coinsOwned) {
  547. totalElement.css("color", "red");
  548. }
  549. else {
  550. totalElement.css("color", "");
  551. }
  552. }
  553. }
  554.  
  555. currentItemSell() {
  556. return $("#modal-market-configure-item-to-sell").val();
  557. }
  558.  
  559. applyOneAmountSell() {
  560. const item = this.currentItemSell();
  561. const owned = IdlePixelPlus.getVarOrDefault(item, 0, "int");
  562. $("#modal-market-configure-item-to-sell-amount").val(Math.min(owned, 1));
  563. this.applyTotalSell();
  564. }
  565.  
  566. applyMaxAmountSell(minus1=false) {
  567. const item = this.currentItemSell();
  568. let max = IdlePixelPlus.getVarOrDefault(item, 0, "int");
  569. if(minus1) {
  570. max--;
  571. }
  572. if(max < 0) {
  573. max = 0;
  574. }
  575. $("#modal-market-configure-item-to-sell-amount").val(max);
  576. this.applyTotalSell();
  577. }
  578.  
  579. applyMinPriceSell() {
  580. const min = parseInt($("#modal-market-configure-item-to-sell-label-lower-limit").text().replace(/[^\d]/g, ""));
  581. $("#modal-market-configure-item-to-sell-price-each").val(min);
  582. this.applyTotalSell();
  583. }
  584.  
  585. applyMidPriceSell() {
  586. const min = parseInt($("#modal-market-configure-item-to-sell-label-lower-limit").text().replace(/[^\d]/g, ""));
  587. const max = parseInt($("#modal-market-configure-item-to-sell-label-upper-limit").text().replace(/[^\d]/g, ""));
  588. const mid = Math.floor((min+max)/2);
  589. $("#modal-market-configure-item-to-sell-price-each").val(mid);
  590. this.applyTotalSell();
  591. }
  592.  
  593. applyMaxPriceSell() {
  594. const max = parseInt($("#modal-market-configure-item-to-sell-label-upper-limit").text().replace(/[^\d]/g, ""));
  595. $("#modal-market-configure-item-to-sell-price-each").val(max);
  596. this.applyTotalSell();
  597. }
  598.  
  599. applyTotalSell() {
  600. const amount = this.parseIntKMBT($("#modal-market-configure-item-to-sell-amount").val());
  601. const price = this.parseIntKMBT($("#modal-market-configure-item-to-sell-price-each").val());
  602. const total = amount*price;
  603. if(isNaN(total)) {
  604. $("#modal-market-configure-item-to-sell-total").text("");
  605. }
  606. else {
  607. $("#modal-market-configure-item-to-sell-total").text(total.toLocaleString());
  608. }
  609. // TODO total w/ tax
  610. }
  611.  
  612. onVariableSet(key, valueBefore, valueAfter) {
  613.  
  614. }
  615.  
  616. }
  617.  
  618. const plugin = new MarketPlugin();
  619. IdlePixelPlus.registerPlugin(plugin);
  620.  
  621. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址