// ==UserScript==
// @name IdlePixel Market Overhaul - Wynaan Fork
// @namespace com.anwinity.idlepixel
// @version 1.5.1
// @description Overhaul of market UI and functionality.
// @author Original Author: Anwinity || Modded By: GodofNades/Zlef/Wynaan
// @license MIT
// @match *://idle-pixel.com/login/play*
// @require https://greasyfork.org/scripts/441206-idlepixel/code/IdlePixel+.js?anticache=20220905
// @require https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.min.js
// @grant none
// ==/UserScript==
(function() {
'use strict';
let marketTimer;
let marketWatcherTimer;
var marketRunning = false;
const LOCAL_STORAGE_KEY_WATCHERS = "plugin_market_watchers";
const LOCAL_STORAGE_KEY_LOG = "plugin_market_log";
const LOCAL_STORAGE_LOG_LIMIT = 100;
const MARKET_HISTORY_URL = "https://data.idle-pixel.com/market/api/getMarketHistory.php";
const MARKET_TRADABLES_URL = "https://data.idle-pixel.com/market/api/getTradables.php";
const MARKET_POSTINGS_URL = "https://idle-pixel.com/market/browse";
const IMAGE_HOST_URL = "https://d1xsc8x7nc5q8t.cloudfront.net/images";
const COIN_ICON_URL = `${IMAGE_HOST_URL}/coins.png`;
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,
ancient_bar: 5000
};
const BONEMEAL_PER = {
bones: 1,
big_bones: 2,
ice_bones: 3,
ashes: 2,
blood_bones: 4
};
const LEVEL_REQ = {
// net
raw_shrimp: "Cooking: 1",
raw_anchovy: "Cooking: 5",
raw_sardine: "Cooking: 10",
raw_crab: "Cooking: 35",
raw_piranha: "Cooking: 50",
// rod
raw_salmon: "Cooking: 10",
raw_trout: "Cooking: 20",
raw_pike: "Cooking: 35",
raw_eel: "Cooking: 55",
raw_rainbow_fish: "Cooking: 70",
// harpoon
raw_tuna: "Cooking: 35",
raw_swordfish: "Cooking: 50",
raw_manta_ray: "Cooking: 75",
raw_shark: "Cooking: 82",
raw_whale: "Cooking: 90",
// plant seeds
dotted_green_leaf_seeds: "Farming: 1<br/>Stop Dying: 15",
red_mushroom_seeds: "Farming: 1<br/>Cant Die",
stardust_seeds: "Farming: 8<br/>Cant Die",
green_leaf_seeds: "Farming: 10<br/>Stop Dying: 25",
lime_leaf_seeds: "Farming: 25<br/>Stop Dying: 40",
gold_leaf_seeds: "Farming: 50<br/>Stop Dying: 60",
crystal_leaf_seeds: "Farming: 70<br/>Stop Dying: 80",
// tree seeds
tree_seeds: "Farming: 10<br/>Stop Dying: 25",
oak_tree_seeds: "Farming: 25<br/>Stop Dying: 40",
willow_tree_seeds: "Farming: 37<br/>Stop Dying: 55",
maple_tree_seeds: "Farming: 50<br/>Stop Dying: 65",
stardust_tree_seeds: "Farming: 65<br/>Stop Dying: 80",
pine_tree_seeds: "Farming: 70<br/>Stop Dying: 85",
redwood_tree_seeds: "Farming: 80<br/>Stop Dying: 92",
// bows
long_bow: "Archery: 25",
// melee
stinger: "Melee: 5 <br /> Invent: 10",
iron_dagger: "Melee: 10 <br /> Invent: 20",
skeleton_sword: "Melee: 20 <br /> Invent: 30",
club: "Melee: 30",
spiked_club: "Melee: 30",
scythe: "Melee: 40",
trident: "Melee: 70",
rapier: "Melee: 90",
// other equipment
bone_amulet: "Invent: 40",
// armour
skeleton_shield: "Melee: 20",
// logs conver rate
logs: "5% <br/> Convert to Charcoal",
oak_logs: "10% <br/> Convert to Charcoal",
willow_logs: "15% <br/> Convert to Charcoal",
maple_logs: "20% <br/> Convert to Charcoal",
stardust_logs: "25% <br/> Convert to Charcoal",
pine_logs: "30% <br/> Convert to Charcoal",
redwood_logs: "35% <br/> Convert to Charcoal"
};
const HEAT_PER = {
raw_chicken: 10,
raw_meat: 40,
// net
raw_shrimp: 10,
raw_anchovy: 20,
raw_sardine: 40,
raw_crab: 75,
raw_piranha: 120,
// rod
raw_salmon: 20,
raw_trout: 40,
raw_pike: 110,
raw_eel: 280,
raw_rainbow_fish: 840,
// harpoon
raw_tuna: 75,
raw_swordfish: 220,
raw_manta_ray: 1200,
raw_shark: 3000,
raw_whale: 5000,
// net (shiny)
raw_shrimp_shiny: 10,
raw_anchovy_shiny: 20,
raw_sardine_shiny: 40,
raw_crab_shiny: 75,
raw_piranha_shiny: 120,
// rod (shiny)
raw_salmon_shiny: 20,
raw_trout_shiny: 40,
raw_pike_shiny: 110,
raw_eel_shiny: 280,
raw_rainbow_fish_shiny: 840,
// harpoon (shiny)
raw_tuna_shiny: 75,
raw_swordfish_shiny: 220,
raw_manta_ray_shiny: 1200,
raw_shark_shiny: 3000,
raw_whale_shiny: 5000,
// net (mega shiny)
raw_shrimp_mega_shiny: 10,
raw_anchovy_mega_shiny: 20,
raw_sardine_mega_shiny: 40,
raw_crab_mega_shiny: 75,
raw_piranha_mega_shiny: 120,
// rod (mega shiny)
raw_salmon_mega_shiny: 20,
raw_trout_mega_shiny: 40,
raw_pike_mega_shiny: 110,
raw_eel_mega_shiny: 280,
raw_rainbow_fish_mega_shiny: 840,
// harpoon (mega shiny)
raw_tuna_mega_shiny: 75,
raw_swordfish_mega_shiny: 220,
raw_manta_ray_mega_shiny: 1200,
raw_shark_mega_shiny: 3000,
raw_whale_mega_shiny: 5000,
//stardust fish
raw_small_stardust_fish: 300,
raw_medium_stardust_fish: 600,
raw_large_stardust_fish: 2000
};
const CHARCOAL_PERC = {
logs: 0.05,
oak_logs: 0.1,
willow_logs: 0.15,
maple_logs: 0.2,
stardust_logs: 0.25,
pine_logs: 0.3,
redwood_logs: 0.35
};
const CATEGORY_RATIOS = {
ores: ["Coins/XP"],
bars: ["Coins/XP"],
bones: ["Coins/Bonemeal"],
logs: ["Coins/Heat", "Coins/Charcoal"],
raw_fish: ["Coins/Energy", "Energy/Heat", "Coins/Heat/Energy"],
cooked_fish: ["Coins/Energy"]
};
const THEME_DEFAULTS = {
default: {
colorPanelsOutline: "#ffffff",
colorPanelsBg: "#ffffff",
colorItemSlotsBg: "#00ffdd",
colorRowOdd: "#c3ebe9",
colorRowEven: "#c3ebe9",
colorText: "#000000",
colorChartLineMax: "#b41414",
colorChartLineAverage: "#3232d2",
colorChartLineMin: "#509125"
},
dark: {
colorPanelsOutline: "#2a2a2a",
colorPanelsBg: "#333333",
colorItemSlotsBg: "#333333",
colorRowOdd: "#333333",
colorRowEven: "#444444",
colorText: "#cccccc",
colorChartLineMax: "#b41414",
colorChartLineAverage: "#0984f7",
colorChartLineMin: "#509125"
}
};
const configurableStyles = document.createElement("style");
document.head.appendChild(configurableStyles);
class MarketPlugin extends IdlePixelPlusPlugin {
constructor() {
super("market", {
about: {
name: GM_info.script.name + " (ver: " + GM_info.script.version + ")",
version: GM_info.script.version,
author: GM_info.script.author,
description: GM_info.script.description
},
config: [
{
label: "------------------------------------------------<br/>General<br/>------------------------------------------------",
type: "label"
},
{
id: "autoMax",
label: "Autofill Max Buy",
type: "boolean",
default: false
},
{
id: "marketSoldNotification",
label: "Notification on item sold",
type: "boolean",
default: true
},
//Zlef
{
id: "clickBrewIng",
label: "Click on a brewing ingredient to go to player market page.",
type: "boolean",
default: true
},
//End Zlef
{
id: "marketGraph",
label: "Show a 7-days price history when browsing items.",
type: "boolean",
default: true
},
{
label: "------------------------------------------------<br/>Table<br/>------------------------------------------------",
type: "label"
},
{
id: "highlightBest",
label: "Highlight Best",
type: "boolean",
default: true
},
{
id: "altIDList",
label: "Player ID blacklist for alts",
type: "string",
max: 200000,
default: "PlaceIDsHere"
},
{
id: "heatPotion",
label: "Account for heat potion use in heat cost",
type: "boolean",
default: true
},
{
id: "extraInfoColumn",
label: "Show Extra Info on table entries",
type: "boolean",
default: true
},
{
id: "categoryColumn",
label: "Show Category on table entries",
type: "boolean",
default: true
},
{
id: "quickBuyColumn",
label: "Show Quick Buy button on table entries",
type: "boolean",
default: true
},
{
id: "quickBuyAmount",
label: "Quick Buy button amount (0 = max)",
type: "number",
default: 1
},
{
id: "quickBuyAllNeedsAltKey",
label: "Require Alt-key when right-clicking to quick-buy all",
type: "boolean",
default: true
},
{
label: "------------------------------------------------<br/>Theme<br/>------------------------------------------------",
type: "label"
},
{
id: "condensed",
label: "Condensed UI",
type: "boolean",
default: true
},
{
id: "theme",
label: "Bundled theme",
type: "select",
options: ["Default", "Dark"],
default: "Default"
},
{
id: "colorTextEnabled",
label: "Change text color",
type: "boolean",
default: false
},
{
id: "colorText",
label: "Text color",
type: "color",
default: THEME_DEFAULTS.default.text
},
{
id: "colorItemSlotsBgEnabled",
label: "Change item slots background color",
type: "boolean",
default: false
},
{
id: "colorItemSlotsBg",
label: "Panels background color",
type: "color",
default: THEME_DEFAULTS.default.bgItemSlots
},
{
id: "colorPanelsBgEnabled",
label: "Change panels background color",
type: "boolean",
default: false
},
{
id: "colorPanelsBg",
label: "Panels background color",
type: "color",
default: THEME_DEFAULTS.default.bgPanels
},
{
id: "colorPanelsOutlineEnabled",
label: "Change panels outline color",
type: "boolean",
default: false
},
{
id: "colorPanelsOutline",
label: "Panels outline color",
type: "color",
default: THEME_DEFAULTS.default.bgOutline
},
{
id: "colorRowAccentsEnabled",
label: "Change table row accent colors",
type: "boolean",
default: false
},
{
id: "colorRowOdd",
label: "Row accent color 1",
type: "color",
default: THEME_DEFAULTS.default.rowAccent1
},
{
id: "colorRowEven",
label: "Row accent color 2",
type: "color",
default: THEME_DEFAULTS.default.rowAccent2
},
{
id: "colorChartLineEnabled",
label: "Change history chart line colors",
type: "boolean",
default: false
},
{
id: "colorChartLineMax",
label: "History chart max price line color",
type: "color",
default: THEME_DEFAULTS.default.colorChartLineMax
},
{
id: "colorChartLineAverage",
label: "History chart average price line color",
type: "color",
default: THEME_DEFAULTS.default.colorChartLineAverage
},
{
id: "colorChartLineMin",
label: "History chart min price line color",
type: "color",
default: THEME_DEFAULTS.default.colorChartLineMin
}
]
});
this.lastBrowsedItem = "all";
this.lastCategoryFilter = "all";
this.historyChart = undefined;
this.marketAverages = {};
this.pendingConfirmationPurchaseLog = {};
this.currentTableData = undefined;
this.lastSortIndex = 0;
this.loginDone = false;
}
onConfigsChanged() {
this.applyCondensed(this.getConfig("condensed"));
this.loadStyles();
if(this.getConfig("marketSoldNotification")) {
this.updateMarketNotifications();
} else {
clearInterval(marketTimer);
clearInterval(marketWatcherTimer);
marketRunning = false;
$("#market-sidecar").hide();
}
if(this.getConfig("marketGraph")) {
$("#history-chart-div").hide();
}
if(this.loginDone)
this.refreshMarket(false);
}
addMarketNotifications() {
const sideCar = document.createElement('span');
sideCar.id = `market-sidecar`;
sideCar.onclick = function () {
IdlePixelPlus.plugins.market.collectMarketButton();
}
sideCar.style='margin-right: 4px; margin-bottom: 4px; display: none; cursor: pointer;';
var elem = document.createElement("img");
elem.setAttribute("src", "https://idlepixel.s3.us-east-2.amazonaws.com/images/player_market.png");
const sideCarIcon = elem;
sideCarIcon.className = "w20";
const sideCarDivLabel = document.createElement('span');
sideCarDivLabel.id = `market-sidecar-coins`;
sideCarDivLabel.innerText = ' 0';
sideCarDivLabel.className = 'color-white'
sideCar.append(" (", sideCarIcon, sideCarDivLabel, ")");
document.querySelector('#item-display-coins').after(sideCar);
$("#market-sidecar").hide();
};
collectMarketButton() {
$("#market-sidecar").hide();
document.querySelectorAll(`button[id^=player-market-slot-collect-amount]`).forEach(b => {
const collect = parseInt(b.textContent.replace(/[^0-9]/g,''));
if(collect > 0){
b.click();
}
});
}
updateMarketNotifications() {
if(!marketRunning) {
marketRunning = true;
marketTimer = setInterval(function() {
websocket.send("MARKET_REFRESH_SLOTS");
setTimeout(function() {
const total = [1, 2, 3].map(n => {
const collect = parseInt($(`button#player-market-slot-collect-amount-${n}`).text().replace(/\D/g,''));
return isNaN(collect) ? 0 : collect;
}).reduce((a, b) => a + b, 0);
if(total > 0) {
$("#market-sidecar-coins").text(" " + total.toLocaleString());
$("#market-sidecar").show();
} else {
$("#market-sidecar-coins").text(" " + total.toLocaleString());
$("#market-sidecar").hide();
}
}, 50);
}, 10000);
marketWatcherTimer = setInterval(function() {
IdlePixelPlus.plugins.market.checkWatchers();
}, 30000);
}
}
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");
}
}
getStyleFromConfig(enableId, colorId) {
return this.getConfig(enableId) ? this.getConfig(colorId) : THEME_DEFAULTS[this.getConfig("theme").toLowerCase()][colorId];
}
loadStyles() {
const colorText = this.getStyleFromConfig("colorTextEnabled", "colorText");
const colorPanelsOutline = this.getStyleFromConfig("colorPanelsOutlineEnabled", "colorPanelsOutline");
const colorRowOdd = this.getStyleFromConfig("colorRowAccentsEnabled", "colorRowOdd");
const colorRowEven = this.getStyleFromConfig("colorRowAccentsEnabled", "colorRowEven");
const colorItemSlotsBg = this.getStyleFromConfig("colorItemSlotsBgEnabled", "colorItemSlotsBg");
const colorPanelsBg = this.getStyleFromConfig("colorPanelsBgEnabled", "colorPanelsBg");
const styles = `
#market-table, #market-log-table {
margin-top: 0.5em !important;
border-spacing:0 4px !important;
border-collapse: separate;
background: ${colorPanelsOutline} !important;
border-width: 4px;
border-radius: 5pt;
padding: 4px;
> * tr th {
background: ${colorPanelsOutline};
color: ${colorText};
}
> * tr:nth-child(even) {
background: ${colorRowOdd};
color: ${colorText};
}
> * tr:nth-child(odd) {
background: ${colorRowEven};
color: ${colorText};
}
> * tr.cheaper {
background-color: rgb(50, 205, 50, 0.25);
}
> * td {
background: inherit;
color: inherit;
&:first-child {
border-top-left-radius: 5pt;
border-bottom-left-radius: 5pt;
}
&:last-child {
border-top-right-radius: 5pt;
border-bottom-right-radius: 5pt;
}
> button {
border-radius: 3pt;
border: 2px solid #00000022;
padding: 4px;
box-shadow: none;
background-color: ${colorPanelsOutline};
color: ${colorText};
&:disabled {
color: ${colorText + "55"};
pointer-events: none;
}
}
}
}
div[id^=player-market-slot] {
border-spacing:0 4px;
border-collapse: separate;
border-radius: 5pt;
border: 2px solid #00000022;
background: ${colorItemSlotsBg};
color: ${colorText};
> div, span {
color: ${colorText} !important;
}
> button {
border-radius: 5pt;
border: 2px solid #00000022;
box-shadow: none;
}
}
div[id^=player-market-slot-empty] {
&:hover {
outline: 1px solid ${colorText + "55"};
z-index: 1;
}
> #panel-sell-text {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-size: 20pt;
color: ${colorText + "55"} !important;
}
}
#market-watcher-div {
border-radius: 5pt;
border: 2px solid #00000022;
background: ${colorPanelsBg};
margin: 0px;
color: ${colorText};
> div[id^=watched-item] {
color: black;
}
}
#history-chart-div {
position: relative;
margin: 0 auto;
border-radius: 5pt;
border: 2px solid #00000022;
background: ${colorPanelsBg};
> #history-chart-timespan {
position: absolute;
top: 6px;
right: 6px;
font-size: 10pt;
border-radius: 3pt;
background-color: ${colorPanelsBg};
color: ${colorText};
&:hover {
cursor: pointer;
}
&:focus-visible {
outline: none;
}
}
}`;
if(this.historyChart) {
this.historyChart.options.scales.x.ticks.color = colorText;
this.historyChart.options.scales.y.ticks.color = colorText;
}
else {
Chart.defaults.color = colorText;
}
configurableStyles.innerHTML = styles;
}
async onLogin() {
this.addMarketNotifications();
if(this.getConfig("marketSoldNotification")) {
this.updateMarketNotifications();
}
const self = this;
$("head").append(`
<style id="styles-market">
#panel-player-market {
&.condensed {
> center {
display: flex;
flex-direction: row;
justify-content: center;
}
& div.player-market-slot-base {
height: 400px;
}
& div.player-market-slot-base hr {
margin-top: 2px;
margin-bottom: 4px;
}
& div.player-market-slot-base br + div.player-market-slot-base br {
display: none;
}
& div.player-market-slot-base[id^="player-market-slot-occupied"] {
> button {
padding: 2px;
}
> button[id^="player-market-slot-see-market"] {
width: 90%;
margin-top: 0.5em;
margin-bottom: 0.5em;
background-color: rgb(46, 137, 221);
}
> h2[id^="player-market-slot-item-item-label"] {
font-size: 1.8rem;
margin-bottom: 0;
}
}
& #market-table th, #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;
}
.hide {
display: none;
}
.bold {
font-weight: bold;
}
.select-item-tradables-catagory {
border-radius: 5pt;
}
#market-table th.actions:hover {
color: gray;
cursor: pointer;
}
.context-menu {
position: absolute;
}
.menu {
display: flex;
flex-direction: column;
background-color: rgb(240, 240, 240);
border-radius: 5pt;
box-shadow: 4px 4px 8px #0e0e0e;
padding: 10px 0;
list-style-type: none;
> li {
font: inherit;
border: 0;
padding: 4px 36px 4px 16px;
width: 100%;
display: flex;
align-items: center;
position: relative;
text-decoration: unset;
color: #000;
transition: 0.5s linear;
-webkit-transition: 0.5s linear;
-moz-transition: 0.5s linear;
-ms-transition: 0.5s linear;
-o-transition: 0.5s linear;
> span:not(:first-child) {
position: absolute;
right: 12px;
}
&:hover {
background:#afafaf;
color: #15156d;
cursor: pointer;
}
}
}
.hoverable-div:hover {
box-shadow: 4px 4px 8px #0e0e0e;
border-color: #252525;
cursor: pointer;
}
#market-log-table th, #market-log-table td {
padding: 2px 4px;
}
</style>
`);
// Market watcher modal
$("#modal-item-input").after(`
<div class="modal modal-dim" id="modal-market-configure-item-watcher" tabindex="-1" style="top: 0px; display: none;" aria-modal="true" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-secondary">ITEM WATCHER</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="center">
<div class="modal-market-sell-image p-2 hard-shadow">
<h2 id="modal-market-configure-item-watcher-label"></h2>
<img id="modal-market-configure-item-watcher-image" width="50px" height="50px" original-width="50px" original-height="50px" src="">
</div>
<br>
<input type="hidden" id="modal-market-configure-item-watcher">
<div class="modal-market-watcher-inputs font-small color-grey p-2 shadow">
<br>
<br>
Limit:
<span class="color-gold" id="modal-market-configure-item-watcher-low-limit">N/A</span>
-
<span class="color-gold" id="modal-market-configure-item-watcher-high-limit">N/A</span>
<span class="color-gold"> each</span>
<br>
<img src="${COIN_ICON_URL}" title="coins">
<input type="text" id="modal-market-configure-item-watcher-price-each" width="30%" placeholder="Price Each" original-width="30%">
<select id="modal-market-configure-item-watcher-mode">
<option value="1">Less than</option>
<option value="2">At least</option>
</select>
<br>
<br>
<br>
<br>
<div>
<input type="button" id="modal-market-configure-item-watcher-cancel-button" data-bs-dismiss="modal" value="Cancel">
<input type="button" id="modal-market-configure-item-watcher-ok-button" onclick="IdlePixelPlus.plugins.market.createMarketWatcher()" class="background-primary hover" value="Create Watcher">
<u class="hover" onclick="alert("You will get a notification when the price crosses the specified threshold.")">?</u>
</div>
<br>
</div>
</div>
</div>
</div>
</div>
</div>`);
// Market table sort menu
$("#panel-player-market").append(`
<div id="market-sort-context-menu" class="context-menu" style="display: none">
<ul class="menu">
<li id="context-menu-price-each-item" onclick='IdlePixelPlus.plugins.market.contextMenuSelectOnClick(\"context-menu-price-each-item\");'>
<span> Price Each</span>
</li>
</ul>
</div>`);
const sellSlotWidth = $(".player-market-slot-base").outerWidth();
document.getElementById("market-table").style.minWidth = sellSlotWidth * 3;
// History chart
$(`#panel-player-market button[onclick^="Market.clicks_browse_player_market_button"]`).parent().before(`
<div id="history-chart-div" style="display:block; margin-bottom: 0.5em; width: ${sellSlotWidth * 3}px; height: 200px;">
<select id="history-chart-timespan" align="right" onchange='IdlePixelPlus.plugins.market.fetchMarketHistory();'>
<option value="1d">24 Hours</option>
<option value="7d" selected="selected">7 Days</option>
<option value="30d">30 Days</option>
<option value="60d">60 Days</option>
<option value="120d">120 Days</option>
</select>
<canvas id="history-chart" style="display: block;" align="middle">
</div>`);
Object.assign(Chart.defaults.datasets.line, {
fill: false,
tension: 0.3,
borderWidth: 2,
pointRadius: 1
});
// Market watcher
$("#notifications-area").children().last().after(`
<div id="notification-market-watcher" class="notification hover hide" onclick='switch_panels(\"panel-player-market\");' style="margin-right: 4px; margin-bottom: 4px; background-color: rgb(183, 68, 14);">
<img src="https://idlepixel.s3.us-east-2.amazonaws.com/images/player_market.png" class="w20" title="market_alert">
<span id="notification-market-item-label" class="color-white"> Market Alert</span>
</div>`);
$("#history-chart-div").prev().before(`
<center>
<div id="market-watcher-div" class="select-item-tradables-catagory shadow" align="left" style="width: ${sellSlotWidth * 3}px; display: none;">
<span class="bold">Active watchers</span>
<hr style="margin-top: 2px; margin-bottom: 4px;">
</div>
</center>`);
// 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.applyLowestPriceSell()">lowest</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>
`);
// Extra buttons beside <BROWSE PLAYER MARKET>
$(`#panel-player-market button[onclick^="Market.clicks_browse_player_market_button"]`)
.first()
.after(`<button id="refresh-market-table-button" type="button" style="height: 44px; margin-left: 0.5em" onclick="IdlePixelPlus.plugins.market.refreshMarket(true);">
Refresh
</button>`)
.after(`<button id="watch-market-item-button" type="button" style="height: 44px; margin-left: 0.5em" onclick="IdlePixelPlus.plugins.market.watchItemOnClick()">
Watch Item
</button>`);
document.querySelectorAll(`button[id^=player-market-slot-collect-amount]`).forEach(b => {
// Add See Market button
const id = b.id.match(/[1-3]/)[0];
b.nextElementSibling.remove();
b.insertAdjacentHTML("afterend", `<button type="button" id="player-market-slot-see-market-${id}" onclick="IdlePixelPlus.plugins.market.seeMarketOnClick(${id})">See Market</button>`);
// Add event to reset collection button
b.addEventListener("click", () => {
const item = document.getElementById(`player-market-slot-item-item-label-${id}`).textContent.toLowerCase().replace(/\s/g, "_");
const price_each = parseInt(document.getElementById(`player-market-slot-item-price-each-${id}`).textContent.replace(/[^0-9]+/g, ""));
const amount = b.textContent.replace(/[^0-9]+/g, "") / price_each;
this.saveLogToLocalStorage({
item: item,
amount: amount,
price_each: price_each,
transaction_type: "Sale"
});
b.textContent = b.textContent.replace(/[0-9,]+/, '0');
$("#market-sidecar").hide();
this.refreshMarket(false);
});
});
document.querySelectorAll(`span[id^=player-market-slot-expires]`).forEach(s => s.previousElementSibling.remove());
// Refresh market on purchase
const purchaseButton = document.querySelector(`input[onclick*="Market.purchase_item()"]`);
if(purchaseButton)
purchaseButton.addEventListener("click", () => this.refreshMarket(false));
sellAmountInput.on("input change", () => this.applyTotalSell());
sellPriceInput.on("input change", () => this.applyTotalSell());
// Zlef
// Add buttons to brewing ingredients
const parentDiv = document.getElementById("panel-brewing");
// Loop through all itembox elements within the parent div
parentDiv.querySelectorAll('itembox').forEach((itemBox) => {
// Check if it contains 'Primary Ingredient' or 'Secondary Ingredient'
const tooltip = itemBox.getAttribute("data-bs-original-title");
if (tooltip && (tooltip.includes("Primary Ingredient") || tooltip.includes("Secondary Ingredient"))) {
// Add click event to the itembox
itemBox.addEventListener("click", () => this.brewingIngClicked(itemBox));
}
});
//End Zlef
// Observer for brewing modal change
const brewingModal = document.getElementById("modal-brew-ingredients");
const brewingModalObserverOptions = { childList: true, subtree: true};
const brewingModalObserver = new window.MutationObserver((mutationRecords) => {
brewingModalObserver.disconnect();
const record = mutationRecords[0];
let totalCost = 0;
const promises = Array.from(record.addedNodes).map((async (node) => {
if(node.nodeName === "IMG" && node.nextSibling.nodeName === "#text") {
const item = node.src.match(/\/([a-zA-Z0-9_]+)\.png$/)[1];
if(Market.tradables.find(t => t.item === item)) {
const qty = node.nextSibling.textContent.match(/[0-9]+/)[0];
const response = await fetch(`${MARKET_POSTINGS_URL}/${item}/`);
const data = await response.json();
let currentMarketMinPrice = Math.min(...data.map(datum => datum.market_item_price_each));
if(!isFinite(currentMarketMinPrice)) { // If item isn't currently on sale, use market average value instead
currentMarketMinPrice = this.marketAverages[item];
}
const displayedValue = (qty * currentMarketMinPrice > 1000) ? `${(qty * currentMarketMinPrice / 1000).toFixed(2)}k` : qty * currentMarketMinPrice;
totalCost += qty * currentMarketMinPrice;
node.nextSibling.textContent += ` (`;
node.nextElementSibling.insertAdjacentHTML("beforebegin", `<img src="${COIN_ICON_URL}" title="coins"> ${displayedValue})`);
}
}
})
);
Promise.all(promises).then(() => {
const totalCostElement = document.getElementById("brewing-total-cost");
const totalCostStr = `Estimated total cost: ${totalCost > 1000 ? (totalCost / 1000).toFixed(2) + "k" : totalCost}`;
if(totalCostElement)
totalCostElement.textContent = totalCostStr;
else
record.target.parentNode.insertAdjacentHTML("afterend", `<span id="brewing-total-cost" class="colorg-grey">${totalCostStr}</span>`);
brewingModalObserver.observe(brewingModal, brewingModalObserverOptions);
});
});
brewingModalObserver.observe(brewingModal, brewingModalObserverOptions);
if(this.getConfig("condensed")) {
// Remove <br> from between <Amount left> and <Price each>, and reinsert it above title
document.querySelectorAll(`span[id^="player-market-slot-item-amount-left"]`).forEach(e => {
const br = e.parentNode.removeChild(e.nextElementSibling);
e.parentNode.querySelector(`h2[id^="player-market-slot-item-item-label"]`).before(br);
});
}
const buyModal = $("#modal-market-purchase-item");
const buyAmountInput = buyModal.find("#modal-market-purchase-item-amount-input");
$(document).on('click', '[onclick*="Modals.market_purchase_item"]', this.handlePurchaseClick.bind(this));
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());
// Remove sell buttons
document.querySelectorAll("div[id^=player-market-slot-empty] button").forEach(b => {
b.parentElement.onclick = b.onclick;
const div = document.createElement("div");
div.setAttribute("id", "panel-sell-text");
div.classList.add("hover");
div.innerText = "Sell an item";
b.replaceWith(div);
});
// wrap Market.browse_get_table to capture last selected
Market.browse_get_table = function(item) {
return self.browseGetTable(item, true);
}
// Wrap Market.purchase_item to send to log
const original_purchase_item = Market.purchase_item;
Market.purchase_item = function() {
const item = document.getElementById("modal-market-purchase-item-label").textContent.toLowerCase().replace(/\s/g, "_");
const amount = get_number_with_letters(document.getElementById("modal-market-purchase-item-amount-input").value);
const price_each = parseInt(document.getElementById("modal-market-purchase-item-price-each").value.replace(/[^0-9]+/g, ""));
IdlePixelPlus.plugins.market.storeLogPendingConfirmation(item, amount, price_each, "Purchase");
original_purchase_item.apply(this);
}
// Add event listener to websocket to catch purchase confirmations
websocket.connected_socket.addEventListener("message", (e) => {
if(e.data.includes("OPEN_DIALOGUE=")) {
const values = e.data.substring(e.data.indexOf('=')+1);
if(values.includes("MARKET PURCHASE") && values.includes("Successfully purchased from player market!")) {
this.saveLogToLocalStorage(this.pendingConfirmationPurchaseLog);
this.pendingConfirmationPurchaseLog = {};
}
}
})
// Edit tradables modal category names
new window.MutationObserver((mutationRecords) => {
const childList = mutationRecords.filter(record => record.type === "childList")[0];
if(childList && childList.target && childList.target.id === "modal-market-select-item-section") {
const elements = document.getElementById(childList.target.id).querySelectorAll(".select-item-tradables-catagory");
elements.forEach(e => {
let isSellModal = false;
e.classList.add("bold");
e.innerHTML = e.innerHTML.replace(/[a-zA-Z_]+<hr>/, e.textContent.split("_").map(w => w[0].toUpperCase() + w.slice(1).toLowerCase()).join(" ") + "<hr>");
e.querySelectorAll("div").forEach(d => {
isSellModal |= /Modals\.market_configure_item_to_sell/.test(d.onclick.toString());
if(d.parentNode.textContent.toLowerCase() != "all") {
d.addEventListener("click", function(event) {
event.stopPropagation();
});
const match = d.onclick.toString().match(/(Modals\.market_configure_item_to_sell|Market\.browse_get_table)\(\"([a-zA-Z0-9_]+)\"/);
if(match) {
d.setAttribute("data-bs-toggle", "tooltip");
d.setAttribute("data-bs-placement", "top");
d.setAttribute("data-boundary", "window");
d.setAttribute("title", Items.get_pretty_item_name(match[2]));
}
}
});
if(!isSellModal) {
e.onclick = () => this.filterButtonOnClick(e.textContent.toLowerCase().replace(" ", "_"));
e.classList.add("hoverable-div");
}
});
}
}).observe(document.getElementById("modal-market-select-item"), {
childList: true,
subtree: true
});
// Player ID display
var playerID = var_player_id;
$(`#search-username-hiscores`).after(`<span id="player_id">(ID: ${playerID})</span>`);
this.onConfigsChanged();
this.createMarketLogPanel();
this.loadStyles();
this.applyLogLocalStorage();
this.applyWatchersLocalStorage();
this.checkWatchers();
this.getGlobalMarketHistoryAverages(7);
this.preloadMarketTradables();
this.loginDone = true;
}
async fetchBrowseResult(item) {
const response = await fetch(`${MARKET_POSTINGS_URL}/${item}/`);
return response.json();
}
browseGetTable(item, updateGraph) {
const self = this;
if(item != this.lastBrowsedItem) {
self.lastSortIndex = 0;
}
this.lastBrowsedItem = item;
if(item == "all") {
$("#watch-market-item-button").hide();
$("#history-chart-div").hide();
}
else {
$("#watch-market-item-button").show();
$("#modal-market-configure-item-watcher-image").attr("src", this.getItemIconUrl(item));
$("#modal-market-configure-item-watcher-label").text(item.split("_").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" "));
try {
if(this.getConfig("marketGraph") && updateGraph) {
self.fetchMarketHistory(item);
}
} catch(err) {
console.log(err);
}
}
// 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_POSTINGS_URL}/${item}/`).done(async function(data) {
const xpMultiplier = DonorShop.has_donor_active(IdlePixelPlus.getVar("donor_bonus_xp_timestamp")) ? 1.1 : 1;
const listofAlts = IdlePixelPlus.plugins.market.getConfig("altIDList").replace(";",",").replace(/\s?,\s?/g, ",").toLowerCase().split(',').map(altId => parseInt(altId));
const useHeatPot = self.getConfig("heatPotion");
if(data.find(datum => ["logs", "raw_fish"].includes(datum.market_item_category)) !== undefined) {
var coinsPerHeat = 100000;
const logsData = await self.fetchBrowseResult("logs");
coinsPerHeat = 1.01 * Math.min(...logsData.map(datum => datum.market_item_price_each / Cooking.getHeatPerLog(datum.market_item_name)));
}
// Removes the alts listing from market and calculations
data = data.filter(datum => listofAlts.indexOf(parseInt(datum.player_id)) == -1);
data.forEach(datum => {
//console.log(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 = isNaN(perCoin) ? "" : `${perCoin.toFixed(perCoin < 10 ? 2 : 1)} coins/xp`;
datum.levelReq = "N/A";
datum.ratios = [perCoin];
self.setBest(best, bestList, datum, perCoin);
break;
}
case "logs": {
let perCoin = (priceAfterTax / (Cooking.getHeatPerLog(datum.market_item_name) * (useHeatPot ? 2 : 1)));
let sDPerCoin = (4000 / priceAfterTax);
const charcoalMultiplier = 1 * (window.var_titanium_charcoal_foundry_crafted ? 2 : 1) * (window.var_green_charcoal_orb_absorbed ? 2 : 1);
let charPerCoin = ((priceAfterTax / CHARCOAL_PERC[datum.market_item_name]) / charcoalMultiplier);
let levelReq = (LEVEL_REQ[datum.market_item_name]);
datum.perCoin = perCoin;
datum.levelReq = levelReq;
datum.sDPerCoin = sDPerCoin;
datum.charPerCoin = charPerCoin;
datum.ratios = [perCoin, charPerCoin];
if (datum.market_item_name == 'stardust_logs') {
datum.perCoinLabel = `${perCoin.toFixed(perCoin < 10 ? 2 : 1)} coins/heat<br />${sDPerCoin.toFixed(sDPerCoin < 10 ? 2 : 1)} ~SD/coin<br/>${charPerCoin.toFixed(charPerCoin < 10 ? 2: 1)} ~coins/charcoal`;
}
else {
datum.perCoinLabel = `${perCoin.toFixed(perCoin < 10 ? 2 : 1)} coins/heat<br/>${charPerCoin.toFixed(charPerCoin < 10 ? 2: 1)} ~coins/charcoal`;
}
self.setBest(best, bestList, datum, perCoin);
break;
}
case "raw_fish":{
let perCoin = (priceAfterTax / Cooking.get_energy(datum.market_item_name));
let energy = (Cooking.get_energy(datum.market_item_name));
let heat = (HEAT_PER[datum.market_item_name]);
let perHeat = (energy / heat);
let comboCoinEnergyHeat = ((priceAfterTax + (heat * coinsPerHeat / (useHeatPot ? 2 : 1))) / energy);
let levelReq = (LEVEL_REQ[datum.market_item_name]);
datum.perCoin = comboCoinEnergyHeat;
datum.perHeat = perHeat;
datum.perCoinLabel = `${perCoin.toFixed(perCoin < 10 ? 2 : 1)} coins/energy || ${perHeat.toFixed(perHeat < 10 ? 2 : 1)} energy/heat<br />${comboCoinEnergyHeat.toFixed(comboCoinEnergyHeat < 10 ? 4 : 1)} coins/heat/energy`;
datum.levelReq = levelReq;
datum.ratios = [perCoin, perHeat, comboCoinEnergyHeat];
self.setBest(best, bestList, datum, perCoin);
break;
}
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`;
datum.levelReq = "N/A";
datum.ratios = [perCoin];
self.setBest(best, bestList, datum, 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`;
datum.levelReq = "N/A";
datum.ratios = [perCoin];
self.setBest(best, bestList, datum, perCoin);
break;
}
case "seeds": {
datum.perCoin = Number.MAX_SAFE_INTEGER;
let levelReq = (LEVEL_REQ[datum.market_item_name]);
let sDPerCoin = (14000 / priceAfterTax);
datum.levelReq = levelReq;
datum.sDPerCoin = sDPerCoin;
datum.perCoinLabel = (datum.market_item_name == "stardust_seeds") ? `${sDPerCoin.toFixed(sDPerCoin < 10 ? 2 : 1)} ~SD/Coin` : "";
break;
}
case "armour":
case "other_equipment":
case "weapons": {
datum.perCoin = Number.MAX_SAFE_INTEGER;
datum.perCoinLabel = "";
datum.levelReq = LEVEL_REQ[datum.market_item_name] ? LEVEL_REQ[datum.market_item_name] : "N/A";
break;
}
default: {
datum.perCoin = Number.MAX_SAFE_INTEGER;
datum.perCoinLabel = "";
datum.levelReq = "N/A";
break;
}
}
});
Object.values(bestList).forEach(bestCatList => bestCatList.forEach(datum => datum.best=true));
//console.log(self.lastCategoryFilter);
//console.log(self.lastSortIndex);
//console.log(self.lastBrowsedItem);
if(item !== self.lastBrowsedItem)
self.lastSortIndex = 0;
self.currentTableData = data;
self.filterTable(item === "all" ? self.lastCategoryFilter : (data.length > 0 ? data[0].market_item_category : "all"));
hide_element("market-loading");
show_element("market-table");
});
}
setBest(best, bestList, datum, ratio) {
if(!best[datum.market_item_category]) {
best[datum.market_item_category] = ratio;
bestList[datum.market_item_category] = [datum];
}
else {
if(ratio == best[datum.market_item_category]) {
bestList[datum.market_item_category].push(datum);
}
else if(ratio < best[datum.market_item_category]) {
bestList[datum.market_item_category] = [datum];
best[datum.market_item_category] = ratio;
}
}
}
updateTable() {
let html = `<tr>
<th>ITEM</th>
<th style="width: 60px;"></th>
<th>AMOUNT</th>
<th class="actions" onclick="IdlePixelPlus.plugins.market.marketHeaderOnClick(event);">PRICE EACH</th>`;
if(this.getConfig("extraInfoColumn"))
html += `<th>EXTRA INFO</th>`;
if(this.getConfig("categoryColumn"))
html += `<th>CATEGORY</th>`;
html += `<th>EXPIRES IN</th>`;
if(this.getConfig("quickBuyColumn"))
html += `<th>QUICK BUY
</th>`;
html += `<th style="width: 0px;"><u class="hover" style="font-size: 80%; font-weight: 400;" onclick="alert("You can configure visible table columns in the plugin options.")">?</u></th>`;
html += `</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);
}
this.currentTableData.forEach(datum => {
if(!datum.hidden) {
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 && this.getConfig("highlightBest");
let levelReq = datum.levelReq;
let your_entry = "";
if(Items.getItem("player_id") == player_id) {
your_entry = "<span class='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 style="width: 60px;"><img src="https://d1xsc8x7nc5q8t.cloudfront.net/images/${item_name}.png" /></td>`;
rowHtml += `<td>${amount}</td>`;
rowHtml += `<td><img src="${COIN_ICON_URL}" /> ${Market.get_price_after_tax(price_each)}`;
if(perCoinLabel) {
rowHtml += `<br /><span style="font-size: 80%; opacity: 0.8">${perCoinLabel}</span>`;
}
rowHtml += `</td>`;
if(this.getConfig("extraInfoColumn"))
rowHtml += `<td>${levelReq}</td>`;
if(this.getConfig("categoryColumn"))
rowHtml += `<td>${category}</td>`;
rowHtml += `<td>${Market._get_expire_time(timestamp)}</td>`;
if(this.getConfig("quickBuyColumn")) {
const qbSetting = this.getConfig("quickBuyAmount");
const qbMaxAmount = Math.min(amount, Math.floor(IdlePixelPlus.getVarOrDefault("coins", 0, "int") / (price_each * 1.01)));
const qbAmount = (qbSetting == 0) ? qbMaxAmount : Math.min(qbSetting, amount, Math.floor(IdlePixelPlus.getVarOrDefault("coins", 0, "int") / (price_each * 1.01)));
const qbButtonStr = (qbSetting == 0) ? "Max" : `${qbAmount}`;
rowHtml += `<td>
<button onclick='event.stopPropagation();
IdlePixelPlus.plugins.market.quickBuyOnClick(${market_id}, ${qbAmount});
IdlePixelPlus.plugins.market.storeLogPendingConfirmation(\"${item_name}\", \"${qbAmount}\", \"${Math.floor(price_each * 1.01)}\", \"Purchase\");'
oncontextmenu='IdlePixelPlus.plugins.market.quickBuyOnRightClick(${market_id}, ${qbMaxAmount}, event);
IdlePixelPlus.plugins.market.storeLogPendingConfirmation(\"${item_name}\", \"${qbMaxAmount}\", \"${Math.floor(price_each * 1.01)}\", \"Purchase\");'
${qbMaxAmount == 0 ? "disabled": ""}>
Buy ${qbButtonStr}
</button>
</td>`;
}
rowHtml += `<td style="width:0px;"></td></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;
}
quickBuyOnClick(marketId, amount) {
IdlePixelPlus.sendMessage("MARKET_PURCHASE=" + marketId + "~" + amount);
this.refreshMarket(false);
this.checkWatchers();
}
quickBuyOnRightClick(marketId, amount, event) {
const qbAllNeedsAltKey = this.getConfig("quickBuyAllNeedsAltKey");
event.preventDefault();
event.stopPropagation();
if(!qbAllNeedsAltKey || event.altKey) {
IdlePixelPlus.sendMessage("MARKET_PURCHASE=" + marketId + "~" + amount);
this.refreshMarket(false);
this.checkWatchers();
}
}
filterButtonOnClick(category) {
this.lastSortIndex = 0;
this.lastCategoryFilter = category;
if(category != "all") { // Patch to prevent clicking the "All" button event coming through to the category listener without double-toggling
Modals.toggle("modal-market-select-item");
}
this.browseGetTable("all", true);
}
filterTable(category) {
if(category) {
this.lastCategoryFilter = category;
}
else {
category = this.lastCategoryFilter || "all";
}
this.configureTableContextMenu(category);
this.currentTableData.forEach(datum => {
if(category === "all")
datum.hidden = false;
else
datum.hidden = !(category === datum.market_item_category);
});
this.sortTable(this.lastSortIndex);
this.updateTable();
}
sortTable(sortDataIndex) {
// Split the table data into a visible and hidden array in order to sort the visible one
const visible = this.currentTableData.filter(datum => !datum.hidden);
const hidden = this.currentTableData.filter(datum => datum.hidden);
visible.sort((a, b) => {
switch(sortDataIndex) {
case 0: return a.market_item_price_each - b.market_item_price_each;
case 100: {
const a_avg = isNaN(this.marketAverages[a.market_item_name]) ? 0.001 : this.marketAverages[a.market_item_name];
const b_avg = isNaN(this.marketAverages[b.market_item_name]) ? 0.001 : this.marketAverages[b.market_item_name];
return ((a.market_item_price_each / a_avg) - 1) - ((b.market_item_price_each / b_avg) - 1);
}
default: return a.ratios[sortDataIndex - 1] - b.ratios[sortDataIndex - 1];
}
});
this.currentTableData = visible.concat(hidden);
this.lastSortIndex = sortDataIndex;
}
refreshMarket(disableButtonForABit) {
if(this.lastBrowsedItem) {
this.browseGetTable(this.lastBrowsedItem, false);
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]+/g, "");
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);
}
// Added by Zlef ->
handlePurchaseClick() {
setTimeout(this.displayOwnedInPurchase.bind(this), 100);
}
displayOwnedInPurchase() {
const itemNameElement = $("#modal-market-purchase-item-label");
const itemName = itemNameElement.text();
if (!itemName) {
return;
}
const itemNameForQuery = itemName.toLowerCase().replace(/\s/g, '_');
let itemVar = IdlePixelPlus.getVarOrDefault(itemNameForQuery, "0");
const containerElement = $("#modal-market-purchase-item-image").parent();
// Check if the element already exists before appending
if (!containerElement.find("#amount-owned").length) {
containerElement.append(`<p id="amount-owned">You own: ${itemVar}</p>`);
} else {
// Update the existing element
containerElement.find("#amount-owned").text(`You own: ${itemVar}`);
}
}
brewingIngClicked(itemBox) {
if (this.getConfig("clickBrewIng")) {
const dataItem = itemBox.getAttribute("data-item").toLowerCase();
if(Market.tradables.find(t => t.item === dataItem)) {
this.openMarketToItem(dataItem);
}
}
}
// Function for opening the market to a specific item
openMarketToItem(dataItem) {
// Simulate clicking the Player Market panel
const playerMarketPanel = document.getElementById("left-panel-item_panel-market");
if (playerMarketPanel) {
playerMarketPanel.click();
}
switch_panels('panel-player-market');
const intervalId = setInterval(() => {
// Check if the market table element is present
const marketTable = document.getElementById("market-table");
if (marketTable) {
// If it's present, clear the interval and execute function
clearInterval(intervalId);
Market.browse_get_table(dataItem);
}
}, 100);
}
//End Zlef
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();
}
async applyLowestPriceSell() {
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 item = $("#modal-market-configure-item-to-sell-image").attr("src").match(/\/([a-zA-Z0-9_]+)\.png$/)[1];
const data = await this.fetchBrowseResult(item);
const lowest = Math.min(...data.map(datum => datum.market_item_price_each));
$("#modal-market-configure-item-to-sell-price-each").val(Math.max(Math.min(lowest - 1, max), 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
}
seeMarketOnClick(sellSlotIndex) {
try {
const item = $(`#player-market-slot-item-image-${sellSlotIndex}`).attr("src").match(/\/([a-zA-Z0-9_]+)\.png$/)[1];
this.browseGetTable(item, true);
} catch(err) {
console.error(err);
}
}
async fetchMarketHistory(item) {
const timespanSelect = document.getElementById("history-chart-timespan");
const timespan = timespanSelect.options[timespanSelect.selectedIndex].value;
if(item === undefined)
item = this.lastBrowsedItem;
$("#history-chart-div").show();
const response = await fetch(`${MARKET_HISTORY_URL}?item=${item}&range=${timespan}`);
const data = await response.json();
const splitData = this.splitHistoryData(data, timespan == "1d" ? "hours" : "days");
// Create chart object if uninitialized
if(this.historyChart === undefined){
this.historyChart = new Chart($("#history-chart"), {
type: 'line',
options: {
maintainAspectRatio: false,
scales: {
x: {
grid: {
color: "#77777744"
}
},
y: {
beginAtZero: false,
grid: {
color: "#77777744"
}
}
},
interaction: {
intersect: false,
mode: 'index',
}
}
});
}
this.updateHistoryChart(splitData);
}
updateHistoryChart(data) {
const averagePrices = data.map(datum => Math.round(datum.data.map(d => d.price * d.amount)
.reduce((a, b) => a + b, 0) / datum.data.map(d => d.amount)
.reduce((a, b) => a + b, 0)));
this.historyChart.options.plugins.tooltip.callbacks.footer = (tooltipItems) => {
const amountsSum = data[tooltipItems[0].dataIndex].data.map(datum => datum.amount).reduce((a, b) => a + b, 0);
return `Transaction Volume: ${amountsSum}`;
}
this.historyChart.data = {
labels: data.map(datum => datum.date),
datasets: [{
label: 'Lowest Price',
data: data.map(datum => Math.min(...datum.data.map(d => d.price))),
borderColor: this.getStyleFromConfig("colorChartLineEnabled", "colorChartLineMin")
},
{
label: 'Average Price',
data: averagePrices,
borderColor: this.getStyleFromConfig("colorChartLineEnabled", "colorChartLineAverage")
},
{
label: 'Highest Price',
data: data.map(datum => Math.max(...datum.data.map(d => d.price))),
borderColor: this.getStyleFromConfig("colorChartLineEnabled", "colorChartLineMax")
}]
};
this.historyChart.update();
}
splitHistoryData(data, bucketSize) {
var splitData = [];
data.history.forEach(datum => {
let match;
const date = new Date(datum.datetime);
if(bucketSize == "days")
match = splitData.filter(dd => dd.date.getDate() == date.getDate() && dd.date.getMonth() == date.getMonth());
else if(bucketSize == "hours")
match = splitData.filter(dd => dd.date.getHours() == date.getHours());
if(match.length == 0) {
splitData.push({
date: date,
data: [{price: datum.price, amount: datum.amount}]
});
} else {
match[0].data.push({price: datum.price, amount: datum.amount});
}
});
if(bucketSize == "days")
splitData.forEach(datum => datum.date = datum.date.toString().match(/^[a-zA-Z]+\s([a-zA-Z]+\s[0-9]{1,2})\s/)[1]);
else if(bucketSize == "hours")
splitData.forEach(datum => datum.date = `${datum.date.getHours()}h`);
return splitData;
}
async getGlobalMarketHistoryAverages(timespan) {
const historyResponse = await fetch(`${MARKET_HISTORY_URL}?item=all&range=${timespan}d`);
this.marketAverages = await historyResponse.json()
.then((data) => {
const sumDict = {};
const avgDict = {};
data.history.forEach(datum => {
sumDict[datum.item] = {
sum: sumDict[datum.item] ? sumDict[datum.item]?.sum + datum.price : datum.price,
length: sumDict[datum.item] ? sumDict[datum.item].length + 1 : 1,
}
});
Object.entries(sumDict).forEach(([item, datum]) => {
avgDict[item] = datum.sum / datum.length
});
return avgDict;
});
}
createMarketWatcher() {
const item = $("#modal-market-configure-item-watcher-label").text().toLowerCase().replace(/\s/g, "_");
const value = $("#modal-market-configure-item-watcher-price-each").val();
const lt_gt = $("#modal-market-configure-item-watcher-mode").val() == "1" ? "<" : ">";
Modals.toggle("modal-market-configure-item-watcher");
$("#modal-market-configure-item-watcher-ok-button").val("Create Watcher");
if($("#market-watcher-div").find(`#watched-item-${item}`).length == 0) {
this.createWatcherElement(item, value, lt_gt);
$("#market-watcher-div").show();
}
else {
$(`#watched-item-${item}-label`).text(`${lt_gt} ${value}`);
}
this.saveWatcherToLocalStorage(item, value, lt_gt);
this.checkWatchers();
}
createWatcherElement(item, value, lt_gt) {
$("#market-watcher-div").children().last().after(`
<div id="watched-item-${item}" class="market-tradable-item p-1 m-1 hover shadow" style="background-color:#ffcccc">
<div align="left" onclick='IdlePixelPlus.plugins.market.browseGetTable(\"${item}\", true); event.stopPropagation();'>
<img class="hover" src="https://d1xsc8x7nc5q8t.cloudfront.net/images/search_white.png" width="15px" height="15px" title="search_white">
</div>
<div onclick='IdlePixelPlus.plugins.market.watchedItemOnClick(\"${item}\");' style="margin-top: -15px;">
<div style="display: block;">
<img src="${this.getItemIconUrl(item)}" width="50px" height="50px">
</div>
<div style="display: block;">
<img src="${COIN_ICON_URL}" title="coins">
<span class="market-watched-item" id="watched-item-${item}-label">${lt_gt} ${value}</span>
</div>
</div>
</div>`);
}
deleteMarketWatcher(item) {
$(`#watched-item-${item}`).remove();
if($("#market-watcher-div").find(".market-watched-item").length == 0) {
$("#market-watcher-div").hide();
}
this.removeWatcherFromLocalStorage(item);
}
configureItemWatcherModal(item, create) {
const tradable = Market.tradables.find(t => t.item == item);
$("#modal-market-configure-item-watcher-image").attr("src", this.getItemIconUrl(item));
document.getElementById("modal-market-configure-item-watcher-label").textContent = Items.get_pretty_item_name(item);
document.getElementById("modal-market-configure-item-watcher-low-limit").textContent = tradable.lower_limit;
document.getElementById("modal-market-configure-item-watcher-high-limit").textContent = tradable.upper_limit;
if(create){
$("#modal-market-configure-item-watcher-price-each").val("");
$("#modal-market-configure-item-watcher-mode").val("1");
$("#modal-market-configure-item-watcher-ok-button").prop("value", `Create Watcher`);
$("#modal-market-configure-item-watcher-cancel-button").prop("value", "Cancel");
$("#modal-market-configure-item-watcher-cancel-button").attr("onclick", "");
}
else {
$("#modal-market-configure-item-watcher-price-each").val($(`#watched-item-${item}-label`).text().match(/[0-9]+/)[0]);
$("#modal-market-configure-item-watcher-mode").val($(`#watched-item-${item}-label`).text().match(/[><]/)[0] == "<" ? "1" : "2");
$("#modal-market-configure-item-watcher-ok-button").prop("value", `Edit Watcher`);
$("#modal-market-configure-item-watcher-cancel-button").prop("value", "Delete Watcher");
$("#modal-market-configure-item-watcher-cancel-button").attr("onclick", `IdlePixelPlus.plugins.market.deleteMarketWatcher(\"${item}\")`);
}
}
watchItemOnClick() {
this.configureItemWatcherModal(this.lastBrowsedItem, true);
Modals.toggle("modal-market-configure-item-watcher");
}
watchedItemOnClick(item) {
this.configureItemWatcherModal(item, false);
Modals.toggle("modal-market-configure-item-watcher");
}
checkWatchers() {
const notification = document.getElementById("notification-market-watcher");
const watchedItems = document.querySelectorAll(".market-watched-item");
const promises = Array.from(watchedItems).map((async (watchedItem) => {
const id = watchedItem.id;
const item = id.match(/watched-item-([a-zA-Z0-9_]+)-label/)[1];
const price = watchedItem.textContent.match(/[0-9]+/)[0];
const lt_gt = watchedItem.textContent.match(/[><]/)[0];
//console.log("Running watcher checks..");
const response = await fetch(`../../market/browse/${item}/`);
const data = await response.json();
const sorted = data.map(datum => Math.floor(datum.market_item_price_each * 1.01)).toSorted((a, b) => a - b);
if(sorted.length > 0 && (lt_gt === ">" && sorted[0] >= price) || (lt_gt === "<" && sorted[0] <= price)) {
document.getElementById(`watched-item-${item}`).style.backgroundColor = "#99ffcc";
return Promise.resolve();
}
else {
document.getElementById(`watched-item-${item}`).style.backgroundColor = "#ffcccc";
return Promise.reject();
}
}));
Promise.any(promises).then(() =>
notification.classList.remove("hide")
).catch(() =>
notification.classList.add("hide")
);
}
saveWatcherToLocalStorage(item, value, lt_gt) {
const ls = localStorage.getItem(LOCAL_STORAGE_KEY_WATCHERS);
const newWatcher = {
item: item,
value: value,
lt_gt: lt_gt
};
var jsonData = {};
if(ls) {
jsonData = JSON.parse(ls);
jsonData.watchers = jsonData.watchers.filter(watcher => watcher.item !== item);
jsonData.watchers.push(newWatcher);
}
else {
jsonData = {
watchers: [newWatcher]
};
}
localStorage.setItem(LOCAL_STORAGE_KEY_WATCHERS, JSON.stringify(jsonData));
}
removeWatcherFromLocalStorage(item) {
const ls = localStorage.getItem(LOCAL_STORAGE_KEY_WATCHERS);
var jsonData = {};
if(ls) {
jsonData = JSON.parse(ls);
jsonData.watchers = jsonData.watchers.filter(watcher => watcher.item !== item);
}
localStorage.setItem(LOCAL_STORAGE_KEY_WATCHERS, JSON.stringify(jsonData));
}
applyWatchersLocalStorage() {
const ls = localStorage.getItem(LOCAL_STORAGE_KEY_WATCHERS);
if(ls) {
const jsonData = JSON.parse(ls);
if(jsonData.watchers && jsonData.watchers.length > 0) {
jsonData.watchers.forEach(watcher => {
this.createWatcherElement(watcher.item, watcher.value, watcher.lt_gt);
});
$("#market-watcher-div").show();
}
}
}
configureTableContextMenu(category) {
const contextMenu = document.getElementById("market-sort-context-menu").getElementsByClassName("menu").item(0);
for(let child of Array.from(contextMenu.querySelectorAll('li:not([id="context-menu-price-each-item"])'))) {
child.remove();
}
if(category in CATEGORY_RATIOS) {
for(let i = 0; i < CATEGORY_RATIOS[category].length; i++) {
contextMenu.innerHTML +=`<li id="context-menu-ratio-${i}" onclick='IdlePixelPlus.plugins.market.contextMenuSelectOnClick(\"context-menu-ratio-${i}\");'>
<span> ${CATEGORY_RATIOS[category][i]}</span>
</li>`;
}
}
else if(this.lastSortIndex != 100) {
this.lastSortIndex = 0;
this.contextMenuChangeSelected("context-menu-price-each-item");
}
contextMenu.innerHTML +=`<li id="context-menu-negative-diff" onclick='IdlePixelPlus.plugins.market.contextMenuSelectOnClick(\"context-menu-negative-diff\");'>
<span> Trending Value (7d)</span>
</li>`;
if(this.lastSortIndex == 0)
this.contextMenuChangeSelected("context-menu-price-each-item");
else if(this.lastSortIndex == 100)
this.contextMenuChangeSelected("context-menu-negative-diff");
else
this.contextMenuChangeSelected(`context-menu-ratio-${this.lastSortIndex - 1}`);
}
contextMenuChangeSelected(menuItem) {
const e = document.getElementById("market-sort-context-menu-selected");
if(e)
e.remove();
document.getElementById(menuItem).innerHTML += `<span id="market-sort-context-menu-selected">✔</span>`;
}
contextMenuSelectOnClick(menuItem) {
this.contextMenuChangeSelected(menuItem);
let sortDataIndex = 0;
if(menuItem == "context-menu-negative-diff")
sortDataIndex = 100;
else if(menuItem != "context-menu-price-each-item")
sortDataIndex = parseInt(menuItem.replace(/[^0-9]/g, "")) + 1;
this.sortTable(sortDataIndex);
this.updateTable();
}
marketHeaderOnClick(event) {
document.addEventListener("click", () => document.getElementById("market-sort-context-menu").style.display = "none", { once: true });
var menu = document.getElementById("market-sort-context-menu");
menu.style.display = 'block';
menu.style.left = event.pageX + "px";
menu.style.top = event.pageY + "px";
event.stopPropagation();
}
async preloadMarketTradables() {
const response = await fetch(MARKET_TRADABLES_URL);
const data = await response.json();
Market.tradables = data.tradables;
}
getItemIconUrl(item) {
return `${IMAGE_HOST_URL}/${item}.png`;
}
createMarketLogPanel() {
IdlePixelPlus.addPanel("market-log", "Market Log", function() {
let content = `
<div>
<table id="market-log-table" class="market-table mt-5" width="90%" style="min-width: 900px;" original-width="90%">
</table>
</div>`;
return content;
});
document.getElementById("left-panel-achievements-btn").nextElementSibling.insertAdjacentHTML("afterend",
`<div id="left-panel-item_panel-market-log" onclick="switch_panels('panel-market-log')" class="hover hover-menu-bar-item left-menu-item">
<table class="game-menu-bar-left-table-btn left-menu-item-quests-ach-loot" style="width:100%">
<tbody><tr>
<td style="width:30px;">
<img id="menu-bar-achievements-icon" class="w30" src="https://d1xsc8x7nc5q8t.cloudfront.net/images/player_market.png">
</td>
<td>
MARKET LOG
</td>
</tr>
</tbody></table>
</div>`);
}
storeLogPendingConfirmation(item, amount, price, type) {
this.pendingConfirmationPurchaseLog = {
item: item,
amount: amount,
price_each: price,
transaction_type: type
};
}
saveLogToLocalStorage(log) {
const ls = localStorage.getItem(LOCAL_STORAGE_KEY_LOG);
const currentTime = new Date();
log.timestamp = currentTime.toLocaleString(undefined, {month: 'short', day: 'numeric', hour: '2-digit', hour12: false, minute: '2-digit'});
var jsonData = {};
if(ls) {
jsonData = JSON.parse(ls);
jsonData.logs.unshift(log);
if(jsonData.logs.length > LOCAL_STORAGE_LOG_LIMIT)
jsonData.logs = jsonData.logs.slice(0, LOCAL_STORAGE_LOG_LIMIT);
}
else {
jsonData = {
logs: [log]
};
}
localStorage.setItem(LOCAL_STORAGE_KEY_LOG, JSON.stringify(jsonData));
this.applyLogLocalStorage();
}
applyLogLocalStorage() {
const ls = localStorage.getItem(LOCAL_STORAGE_KEY_LOG);
let html = `<tr>
<th>ITEM</th>
<th style="width: 60px;"></th>
<th>AMOUNT</th>
<th>PRICE EACH</th>
<th>TOTAL</th>
<th>TRANSACTION</th>
<th>TIME</th>
</tr>`;
if(ls) {
const jsonData = JSON.parse(ls);
if(jsonData.logs && jsonData.logs.length > 0) {
jsonData.logs.forEach(log => {
let rowHtml = `<tr>`;
rowHtml += `<td>${Items.get_pretty_item_name(log.item)}</td>`;
rowHtml += `<td style="width: 60px;"><img src="https://d1xsc8x7nc5q8t.cloudfront.net/images/${log.item}.png" /></td>`;
rowHtml += `<td>${log.amount}</td>`;
rowHtml += `<td><img src="${COIN_ICON_URL}" /> ${log.price_each}`;
rowHtml += `<td><img src="${COIN_ICON_URL}" /> ${log.price_each * log.amount}`;
rowHtml += `<td>${log.transaction_type}</td>`;
rowHtml += `<td>${log.timestamp}</td>`;
rowHtml += `</tr>`;
html += rowHtml;
});
}
}
document.getElementById("market-log-table").innerHTML = html;
}
deleteLogLocalStorage() {
localStorage.setItem(LOCAL_STORAGE_KEY_LOG, "");
}
}
const plugin = new MarketPlugin();
IdlePixelPlus.registerPlugin(plugin);
})();