<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.runerealm.org/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3AGadget-livePricesMMG-core.js</id>
	<title>MediaWiki:Gadget-livePricesMMG-core.js - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.runerealm.org/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3AGadget-livePricesMMG-core.js"/>
	<link rel="alternate" type="text/html" href="https://wiki.runerealm.org/index.php?title=MediaWiki:Gadget-livePricesMMG-core.js&amp;action=history"/>
	<updated>2026-04-11T05:10:03Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.41.1</generator>
	<entry>
		<id>https://wiki.runerealm.org/index.php?title=MediaWiki:Gadget-livePricesMMG-core.js&amp;diff=42202&amp;oldid=prev</id>
		<title>Alex at 11:06, 20 October 2024</title>
		<link rel="alternate" type="text/html" href="https://wiki.runerealm.org/index.php?title=MediaWiki:Gadget-livePricesMMG-core.js&amp;diff=42202&amp;oldid=prev"/>
		<updated>2024-10-20T11:06:16Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;a href=&quot;https://wiki.runerealm.org/index.php?title=MediaWiki:Gadget-livePricesMMG-core.js&amp;amp;diff=42202&amp;amp;oldid=869&quot;&gt;Show changes&lt;/a&gt;</summary>
		<author><name>Alex</name></author>
	</entry>
	<entry>
		<id>https://wiki.runerealm.org/index.php?title=MediaWiki:Gadget-livePricesMMG-core.js&amp;diff=869&amp;oldid=prev</id>
		<title>Alex: Created page with &quot;//  // TODO: Do I just do something kind of dumb like use an object here for constant lookup? EXEMPT_FROM_TAX = [   &#039;Old school bond&#039;,   &#039;Chisel&#039;,   &#039;Gardening trowel&#039;,   &#039;Glassblowing pipe&#039;,   &#039;Hammer&#039;,   &#039;Needle&#039;,   &#039;Pestle and mortar&#039;,   &#039;Rake&#039;,   &#039;Saw&#039;,   &#039;Secateurs&#039;,   &#039;Seed dibber&#039;,   &#039;Shears&#039;,   &#039;Spade&#039;,   &#039;Watering can (0)&#039; ];  // Tax is never higher than 5m per item MAX_TAX_AMOUNT = 5000000;  MMG_SMW_DATA_ENDPOINT = &quot;https://oldschool.runescape.wiki/api.php?acti...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.runerealm.org/index.php?title=MediaWiki:Gadget-livePricesMMG-core.js&amp;diff=869&amp;oldid=prev"/>
		<updated>2024-10-13T00:38:13Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;//  // TODO: Do I just do something kind of dumb like use an object here for constant lookup? EXEMPT_FROM_TAX = [   &amp;#039;Old school bond&amp;#039;,   &amp;#039;Chisel&amp;#039;,   &amp;#039;Gardening trowel&amp;#039;,   &amp;#039;Glassblowing pipe&amp;#039;,   &amp;#039;Hammer&amp;#039;,   &amp;#039;Needle&amp;#039;,   &amp;#039;Pestle and mortar&amp;#039;,   &amp;#039;Rake&amp;#039;,   &amp;#039;Saw&amp;#039;,   &amp;#039;Secateurs&amp;#039;,   &amp;#039;Seed dibber&amp;#039;,   &amp;#039;Shears&amp;#039;,   &amp;#039;Spade&amp;#039;,   &amp;#039;Watering can (0)&amp;#039; ];  // Tax is never higher than 5m per item MAX_TAX_AMOUNT = 5000000;  MMG_SMW_DATA_ENDPOINT = &amp;quot;https://oldschool.runescape.wiki/api.php?acti...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;//&lt;br /&gt;
&lt;br /&gt;
// TODO: Do I just do something kind of dumb like use an object here for constant lookup?&lt;br /&gt;
EXEMPT_FROM_TAX = [&lt;br /&gt;
  &amp;#039;Old school bond&amp;#039;,&lt;br /&gt;
  &amp;#039;Chisel&amp;#039;,&lt;br /&gt;
  &amp;#039;Gardening trowel&amp;#039;,&lt;br /&gt;
  &amp;#039;Glassblowing pipe&amp;#039;,&lt;br /&gt;
  &amp;#039;Hammer&amp;#039;,&lt;br /&gt;
  &amp;#039;Needle&amp;#039;,&lt;br /&gt;
  &amp;#039;Pestle and mortar&amp;#039;,&lt;br /&gt;
  &amp;#039;Rake&amp;#039;,&lt;br /&gt;
  &amp;#039;Saw&amp;#039;,&lt;br /&gt;
  &amp;#039;Secateurs&amp;#039;,&lt;br /&gt;
  &amp;#039;Seed dibber&amp;#039;,&lt;br /&gt;
  &amp;#039;Shears&amp;#039;,&lt;br /&gt;
  &amp;#039;Spade&amp;#039;,&lt;br /&gt;
  &amp;#039;Watering can (0)&amp;#039;&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
// Tax is never higher than 5m per item&lt;br /&gt;
MAX_TAX_AMOUNT = 5000000;&lt;br /&gt;
&lt;br /&gt;
MMG_SMW_DATA_ENDPOINT = &amp;quot;https://oldschool.runescape.wiki/api.php?action=ask&amp;amp;query=[[MMG%20JSON::%2B]]%7C%3FMMG%20JSON%7Climit=10000&amp;amp;format=json&amp;quot;&lt;br /&gt;
MAPPING_ENDPOINT = &amp;#039;https://prices.runescape.wiki/api/v1/osrs/mapping&amp;#039;;&lt;br /&gt;
ALLOWED_GRANULARITIES = [&amp;#039;latest&amp;#039;, &amp;#039;5m&amp;#039;, &amp;#039;1h&amp;#039;, &amp;#039;6h&amp;#039;, &amp;#039;24h&amp;#039;];&lt;br /&gt;
&lt;br /&gt;
SHOULD_TAX_ON_BUY = false;&lt;br /&gt;
SHOULD_TAX_ON_SELL = true;&lt;br /&gt;
&lt;br /&gt;
mmgJs = {&lt;br /&gt;
  init: function () {&lt;br /&gt;
    mmgJs.livePrices = {};&lt;br /&gt;
    mmgJs.officialPrices = {};&lt;br /&gt;
    mmgJs.buttonSelect = null;&lt;br /&gt;
&lt;br /&gt;
    // Builds a map of MMG names to tr elements so we can manipulate them later&lt;br /&gt;
    mmgJs.indexTableRows();&lt;br /&gt;
&lt;br /&gt;
    // Creates the buttons to select the price type&lt;br /&gt;
    mmgJs.initButtons();&lt;br /&gt;
&lt;br /&gt;
    // Loads prices and bootstraps the table&lt;br /&gt;
    mmgJs.loadMMGData();&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  initButtons: function () {&lt;br /&gt;
    // Set up the ButtonSelectWidget that toggles between live and official prices&lt;br /&gt;
    var buttonDiv = document.querySelector(&amp;#039;.mmg-list-table-buttons&amp;#039;);&lt;br /&gt;
&lt;br /&gt;
    var officialPricesButton = new OO.ui.ButtonOptionWidget({&lt;br /&gt;
      title: &amp;#039;Official Prices&amp;#039;,&lt;br /&gt;
      label: &amp;#039;Official Prices&amp;#039;,&lt;br /&gt;
      data: mmgJs.updateTableOfficialPrices&lt;br /&gt;
    })&lt;br /&gt;
    var livePricesButton = new OO.ui.ButtonOptionWidget({&lt;br /&gt;
      title: &amp;#039;Live Prices&amp;#039;,&lt;br /&gt;
      label: &amp;#039;Live Prices&amp;#039;,&lt;br /&gt;
      data: function() { mmgJs.initUpdateTableLivePrices(&amp;#039;24h&amp;#039;) }&lt;br /&gt;
    })&lt;br /&gt;
    mmgJs.buttonSelect = new OO.ui.ButtonSelectWidget({ items: [officialPricesButton, livePricesButton] })&lt;br /&gt;
    mmgJs.buttonSelect.on(&amp;#039;select&amp;#039;, function(item) { item.data() })&lt;br /&gt;
    $(buttonDiv).append(mmgJs.buttonSelect.$element)&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  // Build a map of MMG name -&amp;gt; tr for access later&lt;br /&gt;
  indexTableRows: function () {&lt;br /&gt;
    var table = document.querySelector(&amp;#039;.mmg-list-table&amp;#039;)&lt;br /&gt;
    var trs = table.getElementsByClassName(&amp;#039;mmg-list-table-row&amp;#039;);&lt;br /&gt;
    mmgJs.trsIndexedByName = {}&lt;br /&gt;
    for (var i = 0; i &amp;lt; trs.length; i++) {&lt;br /&gt;
      var tr = trs[i];&lt;br /&gt;
      mmgJs.trsIndexedByName[mmgJs.getTrMmgName(tr)] = tr;&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  getTrMmgName: function (thisTr) {&lt;br /&gt;
    return thisTr.querySelector(&amp;#039;a&amp;#039;).title;&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  loadMapping: function (callback) {&lt;br /&gt;
    // Get the live prices mapping data since we will need it to match names and ids (is there a better place to look?)&lt;br /&gt;
    if (mmgJs.mapping) {&lt;br /&gt;
      callback();&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
      $.ajax({&lt;br /&gt;
        type: &amp;quot;GET&amp;quot;,&lt;br /&gt;
        url: MAPPING_ENDPOINT,&lt;br /&gt;
        dataType: &amp;quot;json&amp;quot;,&lt;br /&gt;
        success: function (msg) {&lt;br /&gt;
          mmgJs.mapping = {};&lt;br /&gt;
          for (var index in msg) {&lt;br /&gt;
            mmgJs.mapping[msg[index][&amp;#039;id&amp;#039;]] = msg[index];&lt;br /&gt;
          }&lt;br /&gt;
        },&lt;br /&gt;
        error: function (req) {&lt;br /&gt;
          console.log(&amp;#039;ERROR: Mapping endpoint failed&amp;#039;)&lt;br /&gt;
        }&lt;br /&gt;
      }).done(callback);&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  loadLivePrices: function (granularity) {&lt;br /&gt;
    // Check for mapping&lt;br /&gt;
    var endpoint = &amp;quot;https://prices.runescape.wiki/api/v1/osrs/&amp;quot; + granularity&lt;br /&gt;
    // Get the live prices data&lt;br /&gt;
    $.ajax({&lt;br /&gt;
      type: &amp;quot;GET&amp;quot;,&lt;br /&gt;
      url: endpoint,&lt;br /&gt;
      dataType: &amp;quot;json&amp;quot;,&lt;br /&gt;
      indexValue: { granularity: granularity },&lt;br /&gt;
      success: function (msg) {&lt;br /&gt;
        mmgJs.livePrices[this.indexValue.granularity] = {};&lt;br /&gt;
        for (var key in msg[&amp;#039;data&amp;#039;]) {&lt;br /&gt;
          if (!(key in mmgJs.mapping)) {&lt;br /&gt;
            console.log(&amp;#039;WARNING: Key &amp;#039; + key + &amp;#039; not found in mapping. If the id is 2659 this is expected.&amp;#039;);&lt;br /&gt;
            continue;&lt;br /&gt;
          }&lt;br /&gt;
          var itemName = mmgJs.mapping[key][&amp;#039;name&amp;#039;]&lt;br /&gt;
          mmgJs.livePrices[this.indexValue.granularity][itemName] = msg[&amp;#039;data&amp;#039;][key]&lt;br /&gt;
        }&lt;br /&gt;
        mmgJs.updateTableLivePrices(granularity);&lt;br /&gt;
      },&lt;br /&gt;
      error: function (req) {&lt;br /&gt;
        onFailure()&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  loadMMGData: function () {&lt;br /&gt;
    // Gets the MMG data via SMW. This should be called once per run.&lt;br /&gt;
    $.ajax({&lt;br /&gt;
      type: &amp;quot;GET&amp;quot;,&lt;br /&gt;
      url: MMG_SMW_DATA_ENDPOINT,&lt;br /&gt;
      dataType: &amp;quot;json&amp;quot;,&lt;br /&gt;
      success: function (msg) {&lt;br /&gt;
        mmgJs.mmgData = msg;&lt;br /&gt;
        mmgJs.results = msg[&amp;#039;query&amp;#039;][&amp;#039;results&amp;#039;];&lt;br /&gt;
        mmgJs.storeOfficialPrices(msg);&lt;br /&gt;
        mmgJs.storeMMGIO(msg);&lt;br /&gt;
&lt;br /&gt;
        // Set the default to Live Prices&lt;br /&gt;
        mmgJs.buttonSelect.selectItemByLabel(&amp;#039;Live Prices&amp;#039;);&lt;br /&gt;
      },&lt;br /&gt;
      error: function (req) {&lt;br /&gt;
        console.log(&amp;#039;ERROR: MMG SMW call failed...Aborting&amp;#039;)&lt;br /&gt;
      }&lt;br /&gt;
    });&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  updateTableOfficialPrices: function () {&lt;br /&gt;
    // We only get one price with the official prices so we have to assume buying and selling at that price&lt;br /&gt;
    mmgJs.buyingPriceMap = mmgJs.officialPrices;&lt;br /&gt;
    mmgJs.sellingPriceMap = mmgJs.officialPrices;&lt;br /&gt;
    mmgJs.createList();&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  storeOfficialPrices: function (mmgData) {&lt;br /&gt;
    // TODO: There&amp;#039;s probably a smarter way to get these values, but this is likely not our bottleneck anyway&lt;br /&gt;
&lt;br /&gt;
    mmgJs.officialPrices = {}&lt;br /&gt;
    for (var mmg in mmgData[&amp;#039;query&amp;#039;][&amp;#039;results&amp;#039;]) {&lt;br /&gt;
      var d = mmgData[&amp;#039;query&amp;#039;][&amp;#039;results&amp;#039;][mmg][&amp;#039;printouts&amp;#039;][&amp;#039;MMG JSON&amp;#039;][0];&lt;br /&gt;
      var parsedData = JSON.parse($(&amp;quot;&amp;lt;textarea/&amp;gt;&amp;quot;).html(d).text());&lt;br /&gt;
      var inputs = parsedData[&amp;#039;inputs&amp;#039;];&lt;br /&gt;
      var outputs = parsedData[&amp;#039;outputs&amp;#039;];&lt;br /&gt;
&lt;br /&gt;
      for (var index in inputs) {&lt;br /&gt;
        // TODO: Is there something idiomatic in old js? I think this is for-of now&lt;br /&gt;
        var item = inputs[index];&lt;br /&gt;
        if (item[&amp;#039;pricetype&amp;#039;] == &amp;#039;gemw&amp;#039;) {&lt;br /&gt;
          mmgJs.officialPrices[item[&amp;#039;name&amp;#039;]] = item[&amp;#039;value&amp;#039;]&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      for (var index in outputs) {&lt;br /&gt;
        // TODO: Is there something idiomatic in old js? I think this is for-of now&lt;br /&gt;
        var item = outputs[index];&lt;br /&gt;
        if (item[&amp;#039;pricetype&amp;#039;] == &amp;#039;gemw&amp;#039;) {&lt;br /&gt;
          mmgJs.officialPrices[item[&amp;#039;name&amp;#039;]] = item[&amp;#039;value&amp;#039;]&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  storeMMGIO: function () {&lt;br /&gt;
    mmgJs.parsedResults = {};&lt;br /&gt;
    for (var mmg in mmgJs.results) {&lt;br /&gt;
      var d = mmgJs.results[mmg][&amp;#039;printouts&amp;#039;][&amp;#039;MMG JSON&amp;#039;][0];&lt;br /&gt;
      mmgJs.parsedResults[mmg] = JSON.parse($(&amp;quot;&amp;lt;textarea/&amp;gt;&amp;quot;).html(d).text());&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  initUpdateTableLivePrices: function (granularity) {&lt;br /&gt;
    // Build a price list based on what we asked for&lt;br /&gt;
    if (!ALLOWED_GRANULARITIES.includes(granularity)) {&lt;br /&gt;
      console.log(&amp;#039;ERROR: &amp;#039; + granularity + &amp;#039; is not a supported granularity&amp;#039;);&lt;br /&gt;
    }&lt;br /&gt;
    // We will call updateTableLivePrices via a callback in loadLivePrices if we don&amp;#039;t already have the prices&lt;br /&gt;
    if (mmgJs.livePrices[granularity] === undefined) {&lt;br /&gt;
      mmgJs.loadMapping(function () { mmgJs.loadLivePrices(granularity) });&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
      mmgJs.updateTableLivePrices(granularity);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  updateTableLivePrices: function (granularity) {&lt;br /&gt;
    var granularityMap = mmgJs.livePrices[granularity];&lt;br /&gt;
    mmgJs.buyingPriceMap = {};&lt;br /&gt;
    mmgJs.sellingPriceMap = {};&lt;br /&gt;
    for (var itemName in granularityMap) {&lt;br /&gt;
      if (granularity === &amp;#039;latest&amp;#039;) {&lt;br /&gt;
        mmgJs.buyingPriceMap[itemName] = granularityMap[itemName][&amp;#039;high&amp;#039;];&lt;br /&gt;
        mmgJs.sellingPriceMap[itemName] = granularityMap[itemName][&amp;#039;low&amp;#039;];&lt;br /&gt;
      }&lt;br /&gt;
      else {&lt;br /&gt;
        mmgJs.buyingPriceMap[itemName] = granularityMap[itemName][&amp;#039;avgHighPrice&amp;#039;];&lt;br /&gt;
        mmgJs.sellingPriceMap[itemName] = granularityMap[itemName][&amp;#039;avgLowPrice&amp;#039;];&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    mmgJs.createList();&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  createList: function () {&lt;br /&gt;
&lt;br /&gt;
    for (var mmg in mmgJs.parsedResults) {&lt;br /&gt;
&lt;br /&gt;
      var mmgName = mmgJs.results[mmg][&amp;#039;fulltext&amp;#039;]&lt;br /&gt;
&lt;br /&gt;
      // Identify what row this is&lt;br /&gt;
      var thisTr = mmgJs.trsIndexedByName[mmgName];&lt;br /&gt;
      if (thisTr === undefined) {&lt;br /&gt;
        console.log(&amp;#039;WARNING: MMG not found in the table: &amp;#039; + mmgName);&lt;br /&gt;
        continue;&lt;br /&gt;
      }&lt;br /&gt;
      var numActions = mmgJs.getKphLocalStorage(mmgName) || mmgJs.parsedResults[mmg][&amp;#039;prices&amp;#039;][&amp;#039;default_kph&amp;#039;]&lt;br /&gt;
      if (thisTr.querySelector(&amp;#039;.mmg-kph-selector&amp;#039;) === null) {&lt;br /&gt;
        mmgJs.addCellToggle(thisTr, numActions);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      // Probably want to grab price info and pass it in here (or do we load then populate the rows?)&lt;br /&gt;
      mmgJs.updateTableRow(thisTr, numActions);&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  updateTableRow: function (thisTr, numActions) {&lt;br /&gt;
    //console.log(&amp;#039;Updating row with numActions = &amp;#039; + numActions);&lt;br /&gt;
&lt;br /&gt;
    // Update profit cell&lt;br /&gt;
    var profitCell = thisTr.querySelector(&amp;#039;.mmg-list-table-profit-cell&amp;#039;);&lt;br /&gt;
    // numActions should be value in the box or default&lt;br /&gt;
    var mmg = thisTr.querySelector(&amp;#039;a&amp;#039;).title;&lt;br /&gt;
    var profit = mmgJs.calculateProfit(mmg, mmgJs.buyingPriceMap, mmgJs.sellingPriceMap, numActions);&lt;br /&gt;
&lt;br /&gt;
    // Add the correct class to the table cell&lt;br /&gt;
    if (profit &amp;gt; 0) {&lt;br /&gt;
      profitCell.classList.remove(&amp;#039;coins-neg&amp;#039;)&lt;br /&gt;
      profitCell.classList.add(&amp;#039;coins-pos&amp;#039;)&lt;br /&gt;
    }&lt;br /&gt;
    else if (profit &amp;lt; 0) {&lt;br /&gt;
      profitCell.classList.remove(&amp;#039;coins-pos&amp;#039;)&lt;br /&gt;
      profitCell.classList.add(&amp;#039;coins-neg&amp;#039;)&lt;br /&gt;
    }&lt;br /&gt;
    else {&lt;br /&gt;
      profitCell.classList.remove(&amp;#039;coins-pos&amp;#039;)&lt;br /&gt;
      profitCell.classList.remove(&amp;#039;coins-neg&amp;#039;)&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    profitCell.innerHTML = profit.toLocaleString();&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  addCellToggle: function (thisTr, valueToUse) {&lt;br /&gt;
    var mmgName = mmgJs.getTrMmgName(thisTr);&lt;br /&gt;
&lt;br /&gt;
    var kphCell = thisTr.querySelector(&amp;#039;.mmg-list-table-kph-cell&amp;#039;);&lt;br /&gt;
    var kphField = new OO.ui.NumberInputWidget({&lt;br /&gt;
      min: 0,&lt;br /&gt;
      input: { value: valueToUse },&lt;br /&gt;
      classes: [&amp;#039;mmg-kph-selector&amp;#039;],&lt;br /&gt;
      showButtons: false,&lt;br /&gt;
      title: mmgJs.parsedResults[mmgName][&amp;#039;prices&amp;#039;][&amp;#039;kph_text&amp;#039;] || &amp;#039;Kills per hour&amp;#039;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    function updateThisRow() {&lt;br /&gt;
      mmgJs.setKphLocalStorage(mmgName, kphField.getNumericValue());&lt;br /&gt;
      return mmgJs.updateTableRow(thisTr, kphField.getNumericValue());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    kphField.on(&amp;#039;change&amp;#039;, updateThisRow);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var resetButton = new OO.ui.ButtonWidget({&lt;br /&gt;
      icon: &amp;#039;reload&amp;#039;,&lt;br /&gt;
      label: &amp;#039;Reset&amp;#039;,&lt;br /&gt;
      invisibleLabel: true,&lt;br /&gt;
      classes: [&amp;#039;mmg-kph-refresh-field&amp;#039;]&lt;br /&gt;
    });&lt;br /&gt;
    resetButton.on(&amp;#039;click&amp;#039;, function () {&lt;br /&gt;
      // Reset the key AFTER setValue since setValue would otherwise write default_kph to LS&lt;br /&gt;
      kphField.setValue(mmgJs.parsedResults[mmgName][&amp;#039;prices&amp;#039;][&amp;#039;default_kph&amp;#039;]);&lt;br /&gt;
      mmgJs.resetKphLocalStorage(mmgName);&lt;br /&gt;
    })&lt;br /&gt;
&lt;br /&gt;
    var layout = new OO.ui.ActionFieldLayout(kphField, resetButton, {&lt;br /&gt;
      classes: [&amp;#039;mmg-kph-selector-field&amp;#039;]&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    kphCell.innerHTML = &amp;#039;&amp;#039;;&lt;br /&gt;
    $(kphCell).append(layout.$element);&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  calculateProfit: function (mmg, buyingPriceMap, sellingPriceMap, numActions) {&lt;br /&gt;
    var inputMap = mmgJs.parsedResults[mmg][&amp;#039;inputs&amp;#039;];&lt;br /&gt;
    var outputMap = mmgJs.parsedResults[mmg][&amp;#039;outputs&amp;#039;];&lt;br /&gt;
&lt;br /&gt;
    var inputAmount = mmgJs.calculateValue(inputMap, buyingPriceMap, numActions, SHOULD_TAX_ON_BUY);&lt;br /&gt;
    var outputAmount = mmgJs.calculateValue(outputMap, sellingPriceMap, numActions, SHOULD_TAX_ON_SELL);&lt;br /&gt;
    return Math.floor(outputAmount - inputAmount);&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  getItemPrice: function (item, givenPrice) {&lt;br /&gt;
    // If the item does not use GE prices, always return the price as is&lt;br /&gt;
    if (item[&amp;#039;pricetype&amp;#039;] != &amp;#039;gemw&amp;#039;)&lt;br /&gt;
      return item[&amp;#039;value&amp;#039;];&lt;br /&gt;
    if (givenPrice === undefined || givenPrice === null) {&lt;br /&gt;
      console.log(&amp;#039;WARNING: This item has no price in the price map you gave me! &amp;#039; + item[&amp;#039;name&amp;#039;])&lt;br /&gt;
      return item[&amp;#039;value&amp;#039;]&lt;br /&gt;
    }&lt;br /&gt;
    return givenPrice;&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  calculateValue: function (itemAmountMap, priceMap, numActions, useTax) {&lt;br /&gt;
    var sum = 0;&lt;br /&gt;
    for (var index in itemAmountMap) {&lt;br /&gt;
      // TODO: Is there something idiomatic in old js? I think this is for-of now&lt;br /&gt;
      var item = itemAmountMap[index];&lt;br /&gt;
      // For each item, determine the value&lt;br /&gt;
      // console.log(&amp;quot;Item: &amp;quot; + JSON.stringify(item));&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      // Stub this out to get the correct price&lt;br /&gt;
      var value = mmgJs.getItemPrice(item, priceMap[item[&amp;#039;name&amp;#039;]]);&lt;br /&gt;
      var quantity = item[&amp;#039;qty&amp;#039;];&lt;br /&gt;
      // Might want to always use this value if pricetype is not gemw since that means it isnt on the ge&lt;br /&gt;
      var priceType = item[&amp;#039;pricetype&amp;#039;];&lt;br /&gt;
&lt;br /&gt;
      // Number used will not change based on numActions if isph is true&lt;br /&gt;
      var numberUsed;&lt;br /&gt;
      if (item[&amp;#039;isph&amp;#039;]) {&lt;br /&gt;
        numberUsed = quantity;&lt;br /&gt;
      }&lt;br /&gt;
      else {&lt;br /&gt;
        numberUsed = quantity * numActions;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      if (useTax) {&lt;br /&gt;
        // Subtract tax since it is per item and not per txn&lt;br /&gt;
        sum += numberUsed * mmgJs.applyTax(item[&amp;#039;name&amp;#039;], value)&lt;br /&gt;
      }&lt;br /&gt;
      else {&lt;br /&gt;
        sum += numberUsed * value;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    return sum;&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  applyTax: function (itemName, price) {&lt;br /&gt;
    if (EXEMPT_FROM_TAX.includes(itemName))&lt;br /&gt;
      return price;&lt;br /&gt;
    return price - Math.min(Math.floor(price / 100), MAX_TAX_AMOUNT);&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  /**&lt;br /&gt;
   *  LocalStorage helper methods to retrieve and set values&lt;br /&gt;
   */&lt;br /&gt;
&lt;br /&gt;
  getLSKeyNameForMmg: function (mmgName) {&lt;br /&gt;
    // mmgName should always have a &amp;quot;Money making guide/&amp;quot; prefix&lt;br /&gt;
    // This will work for anything that is a subpage and we could have some default for a top level page, but I don&amp;#039;t want to pollute LS&lt;br /&gt;
    var mmg = mmgName.split(&amp;#039;/&amp;#039;)[1];&lt;br /&gt;
    if (mmg === undefined)&lt;br /&gt;
      return undefined;&lt;br /&gt;
    return mmg + &amp;#039;-mmg-kph&amp;#039;;&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  getKphLocalStorage: function (mmgName) {&lt;br /&gt;
    var lsKey = mmgJs.getLSKeyNameForMmg(mmgName);&lt;br /&gt;
    if (lsKey !== undefined)&lt;br /&gt;
      return localStorage.getItem(lsKey);&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  setKphLocalStorage: function (mmgName, valueToUse) {&lt;br /&gt;
    var lsKey = mmgJs.getLSKeyNameForMmg(mmgName);&lt;br /&gt;
    if (lsKey !== undefined)&lt;br /&gt;
      localStorage.setItem(lsKey, valueToUse);&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  resetKphLocalStorage: function (mmgName) {&lt;br /&gt;
    var lsKey = mmgJs.getLSKeyNameForMmg(mmgName);&lt;br /&gt;
    if (lsKey !== undefined)&lt;br /&gt;
      localStorage.removeItem(lsKey);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(mmgJs.init);&lt;/div&gt;</summary>
		<author><name>Alex</name></author>
	</entry>
</feed>