// ==UserScript==
// @name IdlePixel Market Overhaul
// @namespace com.anwinity.idlepixel
// @version 1.0.17
// @description Overhaul of market UI and functionality.
// @author Anwinity
// @license MIT
// @match *://idle-pixel.com/login/play*
// @grant none
// @require https://greasyfork.org/scripts/441206-idlepixel/code/IdlePixel+.js?anticache=20220905
// ==/UserScript==
(function() {
'use strict';
const XP_PER = {
stone: 0.1,
copper: 1,
iron: 5,
silver: 10,
gold: 20,
promethium: 100,
titanium: 300,
bronze_bar: 5,
iron_bar: 25,
silver_bar: 50,
gold_bar: 100,
promethium_bar: 500,
titanium_bar: 2000
};
const BONEMEAL_PER = {
bones: 1,
big_bones: 2,
ice_bones: 3,
ashes: 2
};
class MarketPlugin extends IdlePixelPlusPlugin {
constructor() {
super("market", {
about: {
name: GM_info.script.name,
version: GM_info.script.version,
author: GM_info.script.author,
description: GM_info.script.description
},
config: [
{
id: "condensed",
label: "Condensed UI",
type: "boolean",
default: true
},
{
id: "sortMethod",
label: "Sort Method",
type: "select",
default: "default",
options: [
{value: "default", label: "Default"},
{value: "timeDESC", label: "Time (Newest First)"},
{value: "timeASC", label: "Time (Newest Last)"},
{value: "priceASC", label: "Price (Cheapest First)"},
{value: "priceDESC", label: "Price (Cheapest Last)"},
]
},
{
id: "highlightBest",
label: "Highlight Best",
type: "boolean",
default: true
},
{
id: "autoMax",
label: "Autofill Max Buy",
type: "boolean",
default: false
},
]
});
this.lastBrowsedItem = "all";
this.lastCategoryFilter = "all";
}
onConfigsChanged() {
this.applyCondensed(this.getConfig("condensed"));
}
applyCondensed(condensed) {
if(condensed) {
$("#panel-player-market").addClass("condensed");
$("#modal-market-select-item").addClass("condensed");
}
else {
$("#panel-player-market").removeClass("condensed");
$("#modal-market-select-item").removeClass("condensed");
}
}
onLogin() {
const self = this;
$("head").append(`
<style id="styles-market">
#market-table {
margin-top: 0.5em !important;
}
#market-table tr.cheaper {
background-color: rgb(50, 205, 50, 0.25);
}
#market-table tr.cheaper > td {
background-color: rgb(50, 205, 50, 0.25);
}
#panel-player-market.condensed > center {
display: flex;
flex-direction: row;
justify-content: center;
}
#panel-player-market.condensed div.player-market-slot-base {
height: 400px;
}
#panel-player-market.condensed div.player-market-slot-base hr {
margin-top: 2px;
margin-bottom: 4px;
}
#panel-player-market.condensed div.player-market-slot-base br + #panel-player-market.condensed div.player-market-slot-base br {
display: none;
}
#panel-player-market.condensed div.player-market-slot-base[id^="player-market-slot-occupied"] button {
padding: 2px;
}
#panel-player-market.condensed #market-table th {
padding: 2px 4px;
}
#panel-player-market.condensed #market-table td {
padding: 2px 4px;
}
#modal-market-select-item.condensed #modal-market-select-item-section .select-item-tradables-catagory {
margin: 6px 6px;
padding: 6px 6px;
}
#modal-market-select-item.condensed #modal-market-select-item-section .select-item-tradables-catagory hr {
margin-top: 2px;
margin-bottom: 2px;
}
#market-category-filters {
display: flex;
flex-direction: row;
justify-content: center;
align-items: start;
flex-wrap: wrap;
margin: 0.25em;
}
#market-category-filters > button {
display: inline-block;
margin: 0.25em;
}
#market-category-filters > button.active {
background-color: #74DBDB;
}
</style>
`);
// modal-market-configure-item-to-sell-amount
const sellModal = $("#modal-market-configure-item-to-sell");
const sellAmountInput = sellModal.find("#modal-market-configure-item-to-sell-amount");
sellAmountInput.after(`
<button type="button" onclick="IdlePixelPlus.plugins.market.applyOneAmountSell()">1</button>
<button type="button" onclick="IdlePixelPlus.plugins.market.applyMaxAmountSell()">max</button>
<button type="button" onclick="IdlePixelPlus.plugins.market.applyMaxAmountSell(true)">max-1</button>
`);
const sellPriceInput = sellModal.find("#modal-market-configure-item-to-sell-price-each").after(`
<button type="button" onclick="IdlePixelPlus.plugins.market.applyMinPriceSell()">min</button>
<button type="button" onclick="IdlePixelPlus.plugins.market.applyMidPriceSell()">mid</button>
<button type="button" onclick="IdlePixelPlus.plugins.market.applyMaxPriceSell()">max</button>
<br /><br />
Total: <span id="modal-market-configure-item-to-sell-total"></span>
`);
sellAmountInput.on("input change", () => this.applyTotalSell());
sellPriceInput.on("input change", () => this.applyTotalSell());
const buyModal = $("#modal-market-purchase-item");
const buyAmountInput = buyModal.find("#modal-market-purchase-item-amount-input");
buyAmountInput.after(`
<button type="button" onclick="IdlePixelPlus.plugins.market.applyOneAmountBuy()">1</button>
<button type="button" onclick="IdlePixelPlus.plugins.market.applyMaxAmountBuy()">max</button>
<br /><br />
Total: <span id="modal-market-purchase-item-total"></span>
<br />
Owned: <item-display data-format="number" data-key="coins"></item-display>
`);
buyAmountInput.on("input change", () => this.applyTotalBuy());
// wrap Market.browse_get_table to capture last selected
const original_market_browse = Market.browse_get_table;
Market.browse_get_table = function(item) {
return self.browseGetTable(item)
.always(() => {
self.filterTable();
});
}
$("#market-table").css("margin-top", "24px");
$("#market-table").parent().before(`<div id="market-category-filters"><div>`);
// wrap Market.load_tradables to populate category filters
const original_load_tradables = Market.load_tradables;
Market.load_tradables = function(data) {
original_load_tradables.apply(this, arguments);
self.createFilterButtons();
}
self.createFilterButtons();
$(`#panel-player-market button[onclick^="Market.clicks_browse_player_market_button"]`)
.first()
.after(`<button id="refresh-market-table-button" type="button" style="margin-left: 0.5em" onclick="IdlePixelPlus.plugins.market.refreshMarket(true);">Refresh</button>`);
//$("button.market-remove-button").after('<br><br><br><button onclick="" class="market-rebrowse-button">Browse</button>');
this.onConfigsChanged();
}
browseGetTable(item) {
//console.log(`browseGetTable("${item}")`);
const self = this;
this.lastBrowsedItem = item;
if(item != "all") {
self.lastCategoryFilter = "all";
}
if(item == "all") {
$("#market-category-filters").show();
}
else {
$("#market-category-filters").hide();
}
// A good chunk of this is taking directly from Market.browse_get_table
hide_element("market-table");
show_element("market-loading");
let best = {};
let bestList = {};
return $.get(`../../market/browse/${item}/`).done(function(data) {
const xpMultiplier = DonorShop.has_donor_active(IdlePixelPlus.getVar("donor_bonus_xp_timestamp")) ? 1.1 : 1;
//console.log(data);
data.forEach(datum => {
const priceAfterTax = datum.market_item_price_each * 1.01;
switch(datum.market_item_category) {
case "bars":
case "ores": {
let perCoin = (priceAfterTax / (xpMultiplier*XP_PER[datum.market_item_name]));
datum.perCoin = perCoin;
datum.perCoinLabel = `${perCoin.toFixed(perCoin < 10 ? 2 : 1)} coins/xp`;
if(!best[datum.market_item_category]) {
best[datum.market_item_category] = perCoin;
bestList[datum.market_item_category] = [datum];
}
else {
if(perCoin == best[datum.market_item_category]) {
bestList[datum.market_item_category].push(datum);
}
else if(perCoin < best[datum.market_item_category]) {
bestList[datum.market_item_category] = [datum];
best[datum.market_item_category] = perCoin;
}
}
break;
}
case "logs": {
let perCoin = (priceAfterTax / Cooking.getHeatPerLog(datum.market_item_name));
datum.perCoin = perCoin;
datum.perCoinLabel = `${perCoin.toFixed(perCoin < 10 ? 2 : 1)} coins/heat`;
if(!best[datum.market_item_category]) {
best[datum.market_item_category] = perCoin;
bestList[datum.market_item_category] = [datum];
}
else {
if(perCoin == best[datum.market_item_category]) {
bestList[datum.market_item_category].push(datum);
}
else if(perCoin < best[datum.market_item_category]) {
bestList[datum.market_item_category] = [datum];
best[datum.market_item_category] = perCoin;
}
}
break;
}
case "raw_fish":
case "cooked_fish":{
let perCoin = (priceAfterTax / Cooking.get_energy(datum.market_item_name));
datum.perCoin = perCoin;
datum.perCoinLabel = `${perCoin.toFixed(perCoin < 10 ? 2 : 1)} coins/energy`;
if(!best[datum.market_item_category]) {
best[datum.market_item_category] = perCoin;
bestList[datum.market_item_category] = [datum];
}
else {
if(perCoin == best[datum.market_item_category]) {
bestList[datum.market_item_category].push(datum);
}
else if(perCoin < best[datum.market_item_category]) {
bestList[datum.market_item_category] = [datum];
best[datum.market_item_category] = perCoin;
}
}
break;
}
case "bones": {
let perCoin = (priceAfterTax / BONEMEAL_PER[datum.market_item_name]);
datum.perCoin = perCoin;
datum.perCoinLabel = `${perCoin.toFixed(perCoin < 10 ? 2 : 1)} coins/bonemeal`;
if(!best[datum.market_item_category]) {
best[datum.market_item_category] = perCoin;
bestList[datum.market_item_category] = [datum];
}
else {
if(perCoin == best[datum.market_item_category]) {
bestList[datum.market_item_category].push(datum);
}
else if(perCoin < best[datum.market_item_category]) {
bestList[datum.market_item_category] = [datum];
best[datum.market_item_category] = perCoin;
}
}
break;
}
default: {
datum.perCoin = Number.MAX_SAFE_INTEGER;
datum.perCoinLabel = "";
break;
}
}
});
Object.values(bestList).forEach(bestCatList => bestCatList.forEach(datum => datum.best=true));
const sortMethod = self.getConfig("sortMethod");
switch(sortMethod) {
case "timeDESC": {
data = data.sort((a, b) => b.market_item_post_timestamp - a.market_item_post_timestamp);
break;
}
case "timeASC": {
data = data.sort((a, b) => a.market_item_post_timestamp - b.market_item_post_timestamp);
break;
}
case "priceASC": {
data = data.sort((a, b) => {
if(a.perCoin != b.perCoin && typeof a.perCoin==="number" && typeof b.perCoin==="number") {
return a.perCoin - b.perCoin;
}
return a.market_item_price_each - b.market_item_price_each;
});
// DEBUG
//data.filter(x => x.market_item_category == "cooked_fish").forEach(d => {
// console.log(`${d.market_item_name} ${d.perCoin} ${d.market_item_price_each}`);
//});
//
break;
}
case "priceDESC": {
data = data.sort((a, b) => {
if(a.perCoin != b.perCoin && typeof a.perCoin==="number" && typeof b.perCoin==="number") {
return b.perCoin - a.perCoin;
}
return b.market_item_price_each - a.market_item_price_each;
});
break;
}
}
// console.log(data);
let html = "<tr><th>ITEM</th><th></th><th>AMOUNT</th><th>PRICE EACH</th><th>CATEGORY</th><th>EXPIRES IN</th></tr>";
// in case you want to add any extra data to the table but still use this script
if(typeof window.ModifyMarketDataHeader === "function") {
html = window.ModifyMarketDataHeader(html);
}
data.forEach(datum => {
let market_id = datum.market_id;
let player_id = datum.player_id;
let item_name = datum.market_item_name;
let amount = datum.market_item_amount;
let price_each = datum.market_item_price_each;
let category = datum.market_item_category;
let timestamp = datum.market_item_post_timestamp;
let perCoinLabel = datum.perCoinLabel;
let best = datum.best && self.getConfig("highlightBest");
let your_entry = "";
if(Items.getItem("player_id") == player_id) {
your_entry = "<span class='color-grey font-small'><br /><br />(Your Item)</span>";
}
let rowHtml = "";
rowHtml += `<tr onclick="Modals.market_purchase_item('${market_id}', '${item_name}', '${amount}', '${price_each}'); IdlePixelPlus.plugins.market.applyMaxAmountBuyIfConfigured();" class="hover${ best ? ' cheaper' : '' }">`;
rowHtml += `<td>${Items.get_pretty_item_name(item_name)}${your_entry}</td>`;
rowHtml += `<td><img src="https://d1xsc8x7nc5q8t.cloudfront.net/images/${item_name}.png" /></td>`;
rowHtml += `<td>${amount}</td>`;
rowHtml += `<td><img src="https://d1xsc8x7nc5q8t.cloudfront.net/images/coins.png" /> ${Market.get_price_after_tax(price_each)}`;
if(perCoinLabel) {
rowHtml += `<br /><span style="font-size: 80%; opacity: 0.8">${perCoinLabel}</span>`;
}
rowHtml += `</td>`;
rowHtml += `<td>${category}</td>`;
rowHtml += `<td>${Market._get_expire_time(timestamp)}</td>`;
rowHtml += `</tr>`;
// in case you want to add any extra data to the table but still use this script
if(typeof window.ModifyMarketDataRow === "function") {
rowHtml = window.ModifyMarketDataRow(datum, rowHtml);
}
html += rowHtml;
});
document.getElementById("market-table").innerHTML = html;
hide_element("market-loading");
show_element("market-table");
});
}
createFilterButtons() {
const filters = $("#market-category-filters");
filters.empty();
if(Market.tradables) {
const categories = [];
Market.tradables.forEach(tradable => {
if(!categories.includes(tradable.category)) {
categories.push(tradable.category);
}
});
filters.append(`<button data-category="all" onclick="IdlePixelPlus.plugins.market.filterTable('all')">All</button>`);
categories.forEach(cat => {
filters.append(`<button data-category="${cat}" onclick="IdlePixelPlus.plugins.market.filterTable('${cat}')">${cat.replace(/_/g, " ").replace(/(^|\s)\w/g, s => s.toUpperCase())}</button>`);
});
}
}
filterTable(category) {
if(category) {
this.lastCategoryFilter = category;
}
else {
category = this.lastCategoryFilter || "all";
}
$("#market-category-filters button.active").removeClass("active");
$(`#market-category-filters button[data-category="${category}"]`).addClass("active");
const rows = $("#market-table tbody tr.hover");
if(category=="all") {
rows.show();
}
else {
rows.each(function() {
const row = $(this);
const rowCategory = row.find("td:nth-child(5)").text();
if(category == rowCategory) {
row.show();
}
else {
row.hide();
}
});
}
}
refreshMarket(disableButtonForABit) {
if(this.lastBrowsedItem) {
Market.browse_get_table(this.lastBrowsedItem);
if(disableButtonForABit) { // prevent spam clicking it
$("#refresh-market-table-button").prop("disabled", true);
setTimeout(() => {
$("#refresh-market-table-button").prop("disabled", false);
}, 700);
}
}
}
applyOneAmountBuy() {
$("#modal-market-purchase-item #modal-market-purchase-item-amount-input").val(1);
this.applyTotalBuy();
}
applyMaxAmountBuyIfConfigured() {
if(this.getConfig("autoMax")) {
this.applyMaxAmountBuy();
}
}
applyMaxAmountBuy(minus1=false) {
const coinsOwned = IdlePixelPlus.getVarOrDefault("coins", 0, "int");
const price = parseInt($("#modal-market-purchase-item #modal-market-purchase-item-price-each").val().replace(/[^\d]+/g, ""));
const maxAffordable = Math.floor(coinsOwned / price);
const maxAvailable = parseInt($("#modal-market-purchase-item #modal-market-purchase-item-amount-left").val().replace(/[^\d]+/g, ""));
let max = Math.min(maxAffordable, maxAvailable);
if(minus1) {
max--;
}
if(max < 0) {
max = 0;
}
$("#modal-market-purchase-item #modal-market-purchase-item-amount-input").val(max);
this.applyTotalBuy();
}
parseIntKMBT(s) {
if(typeof s === "number") {
return Math.floor(s);
}
s = s.toUpperCase().replace(/[^\dKMBT]+/, "");
if(s.endsWith("K")) {
s = s.replace(/K$/, "000");
}
else if(s.endsWith("M")) {
s = s.replace(/M$/, "000000");
}
else if(s.endsWith("B")) {
s = s.replace(/B$/, "000000000");
}
else if(s.endsWith("T")) {
s = s.replace(/T$/, "000000000000");
}
return parseInt(s);
}
applyTotalBuy() {
const amount = this.parseIntKMBT($("#modal-market-purchase-item #modal-market-purchase-item-amount-input").val());
const price = this.parseIntKMBT($("#modal-market-purchase-item #modal-market-purchase-item-price-each").val().replace("Price each: ", ""));
const total = amount*price;
const totalElement = $("#modal-market-purchase-item-total");
if(isNaN(total)) {
totalElement.text("");
}
else {
totalElement.text(total.toLocaleString());
const coinsOwned = IdlePixelPlus.getVarOrDefault("coins", 0, "int");
if(total > coinsOwned) {
totalElement.css("color", "red");
}
else {
totalElement.css("color", "");
}
}
}
currentItemSell() {
return $("#modal-market-configure-item-to-sell").val();
}
applyOneAmountSell() {
const item = this.currentItemSell();
const owned = IdlePixelPlus.getVarOrDefault(item, 0, "int");
$("#modal-market-configure-item-to-sell-amount").val(Math.min(owned, 1));
this.applyTotalSell();
}
applyMaxAmountSell(minus1=false) {
const item = this.currentItemSell();
let max = IdlePixelPlus.getVarOrDefault(item, 0, "int");
if(minus1) {
max--;
}
if(max < 0) {
max = 0;
}
$("#modal-market-configure-item-to-sell-amount").val(max);
this.applyTotalSell();
}
applyMinPriceSell() {
const min = parseInt($("#modal-market-configure-item-to-sell-label-lower-limit").text().replace(/[^\d]/g, ""));
$("#modal-market-configure-item-to-sell-price-each").val(min);
this.applyTotalSell();
}
applyMidPriceSell() {
const min = parseInt($("#modal-market-configure-item-to-sell-label-lower-limit").text().replace(/[^\d]/g, ""));
const max = parseInt($("#modal-market-configure-item-to-sell-label-upper-limit").text().replace(/[^\d]/g, ""));
const mid = Math.floor((min+max)/2);
$("#modal-market-configure-item-to-sell-price-each").val(mid);
this.applyTotalSell();
}
applyMaxPriceSell() {
const max = parseInt($("#modal-market-configure-item-to-sell-label-upper-limit").text().replace(/[^\d]/g, ""));
$("#modal-market-configure-item-to-sell-price-each").val(max);
this.applyTotalSell();
}
applyTotalSell() {
const amount = this.parseIntKMBT($("#modal-market-configure-item-to-sell-amount").val());
const price = this.parseIntKMBT($("#modal-market-configure-item-to-sell-price-each").val());
const total = amount*price;
if(isNaN(total)) {
$("#modal-market-configure-item-to-sell-total").text("");
}
else {
$("#modal-market-configure-item-to-sell-total").text(total.toLocaleString());
}
// TODO total w/ tax
}
onVariableSet(key, valueBefore, valueAfter) {
}
}
const plugin = new MarketPlugin();
IdlePixelPlus.registerPlugin(plugin);
})();