Back to Index Page generated: Jun 13, 2026, 7:54:54 PM

Expansion Market Observer

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Enhances the market screen by showing trading information. Trade information includes price average, difference, percentual difference and purchase log. This oxp also gives a trader's rating based on your buys and sells. Enhances the market screen by showing trading information. Trade information includes price average, difference, percentual difference and purchase log. This oxp also gives a trader's rating based on your buys and sells.
Identifier oolite.oxp.spara.market_observer oolite.oxp.spara.market_observer
Title Market Observer Market Observer
Category Equipment Equipment
Author spara spara
Version 3.7 3.7
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Dependent Expansions
  • oolite.oxp.Cholmondeley.Addons_for_Beginners_(Vital_Statistics):1.2
  • oolite.oxp.Norby.Ambience_Collection:1.3
  • oolite.oxp.spara.mo_commodity_markets:1.2.3
  • oolite.oxp.z.phkb.MarketScreenLegalColumnMO:1.0
  • Information URL http://wiki.alioth.net/index.php/MarketObserver n/a
    Download URL https://wiki.alioth.net/img_auth.php/c/cb/Market_observer_3.7.oxz n/a
    License CC-BY-NC-SA 4.0 CC-BY-NC-SA 4.0
    File Size n/a
    Upload date 1610873473

    Relationships Diagram

    Documentation

    Also read http://wiki.alioth.net/index.php/Market%20Observer

    marketObserver_readme_&_license.txt

    Market Observer OXP ver 3.7 (12.10.2020)
    
    Author: spara (Mika Spåra)
    
    _Overview_
    
    This oxp enhances the market screen with valuable data. To keep this enhancement free, ads from various advertisers are shown in the market screen.
    
    _Features_
    
    1. Market Reference Gatherer
    
    This feature monitors the prices of system markets and keeps a list of average prices. This data is printed to the market screen.
    
    2. System Price Difference Viewer
    
    This feature prints the difference of station prices compared to the averages as absolute and percentage value to the market screen. Gives a quick overview of what is cheap and what expensive.
    
    3. Purchase Log
    
    Purchase log is collected from purchaces at the standard market. When selling, the log is purged starting from the most expensive purchases. The most expensive purchase is printed to the market screen. Helps to determine, if it's worth to sell or not. A more detailed view is available in the detailed commodity view (f8-f8).
    
    If cargo is lost, lost cargo is purged from the log when docking at a station.
    
    4. Ads From YAH / Requires a separate Market Ads OXP to be installed
    
    Various ads from Your Ad Here! OXP are shown in the bottom of the market screen.
    
    OXP has an api for adding even more ads to the mix.
    
    * Simply call worldScripts["market_ads_service"]._setHorzAd('filename_of_your_horizontal_ad') in startUp and that's it. Ads need to be in 256x128 resolution.
    
    5. Trader's Rating
    
    Market Observer monitors the profit you gain from buying and selling at the standard market and gives you a rating. Profit is calculated when selling a commodity that's purchase has been logged. No profit is calculated from salvaging or contracting etc, unless some purchased cargo is lost. Player is given a Trader's Rating based on profit made. Rating is shown in the ship manifest and if Market Ads OXP is installed, on the market screen.
    
    First rank at 1500 cr. Highest rank at 1000000 cr.
    
    _Requirements_
    
    * Oolite version 1.82 or higher.
    
    _Credits_
    
    * All ads are selected and cropped from Your Ad Here! oxp and Your Ad Here! forum thread. Ads are made by different Oolite players.
    
    ------
    
    This work is licensed under the Creative Commons Attribution-Noncommercial-Share Alike 4.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/
    

    Equipment

    This expansion declares no equipment.

    Ships

    This expansion declares no ships.

    Models

    This expansion declares no models.

    Scripts

    Path
    Scripts/market_observer.js
    "use strict";
    
    this.name = "market_observer3";
    this.author = "spara";
    this.description = "Show extra market information";
    
    this.startUp = function() {
    	this.$basisLimit = 100 //current avg is weighted with this value
    
    	//init price gatherer
    	if (!missionVariables.marketObserver3) {
    		this.$priceData = new Object();
    	}
    	else {
    		this.$priceData = JSON.parse(missionVariables.marketObserver3);
    	}
    	
    	//init purchase log
    	if (!missionVariables.marketObserverBuyLog3) {
    		this.$buyLog = new Object();
    	}
    	else {
    		this.$buyLog = JSON.parse(missionVariables.marketObserverBuyLog3);
    	}
    	
    	//init contracted goods
    	if (!missionVariables.marketObserverContracted) {
    		this.$contracted = new Object();
    	}
    	else {
    		this.$contracted = JSON.parse(missionVariables.marketObserverContracted);
    	}
    }
    
    this.startUpComplete = function() {
    	//for compatibility with commodity markets oxp. it changes system market in market screen
    	this.$sMarket = system.mainStation.market;
    	//on start up, check for new commodities.
    	var sMarket = this.$sMarket;
    	for (var i in sMarket) {
    		if (!this.$priceData[i]) {
    			this.$updatePriceData();
    			break;
    		}
    	}
    }
    
    this.playerWillSaveGame = function () {
    	missionVariables.marketObserver3 = JSON.stringify(this.$priceData);
    	missionVariables.marketObserverBuyLog3 = JSON.stringify(this.$buyLog);
    	missionVariables.marketObserverContracted = JSON.stringify(this.$contracted);
    }
    
    //mark commodities as contracted to be noted when player buys or sells. contracted goods do not affect trader's rating.
    this.playerEnteredContract = function(type, contract) {
    	if (type === "cargo") {
    		var commodity = contract.co_type;
    		if (this.$contracted[commodity]) {
    			this.$contracted[commodity] += contract.co_amount;
    		}
    		else this.$contracted[commodity] = contract.co_amount
    	}
    }
    
    //release commodities for being marked as contracted.
    this.playerCompletedContract = function(type, result, fee, contract) {
    	if (type === "cargo") {
    		var commodity = contract.co_type;
    		if (this.$contracted[commodity]) {
    			this.$contracted[commodity] -= contract.co_amount;
    			if (this.$contracted[commodity] <= 0) {
    				delete this.$contracted[commodity];
    			}
    		}
    	}
    }
    
    //cancel contracts when entering new galaxy as core does
    this.playerEnteredNewGalaxy = function(galaxyNumber) {
    	this.$contracted = new Object();
    }
    
    //update system prices when exiting WS
    this.shipExitedWitchspace = function() {
    	if (system.mainStation) {
    		this.$updatePriceData();
    		this.$sMarket = system.mainStation.market;
    	}
    }
    
    //update average
    this.$updatePriceData = function() {
    	var sMarket = this.$sMarket;
    	for (var i in sMarket) {
    		//update the average of a known commodity using a weighting from this.$basisLimit
    		if (this.$priceData[i]) {
    			var avgBasis = this.$basisLimit;
    			this.$priceData[i].average = (avgBasis * this.$priceData[i].average + sMarket[i].price) / (avgBasis + 1);
    		}
    		//init a new commodity using average from commodity def
    		else {
    			//there's a rare possibility that the commodity has no price_average
    			if (sMarket[i].price_average) {
    				this.$priceData[i] = {
    					average: sMarket[i].price_average
    				}
    			}
    			else {
    				this.$priceData[i] = {
    					average: sMarket[i].price
    				}
    			}
    		}
    	}
    }
    
    //update purchase log
    this.playerBoughtCargo = function(commodity, units, price) {
    	//there's a contract on commodity
    	if (this.$contracted[commodity]) {
    		var prevQuantity = player.ship.manifest[commodity] - units;
    		var replaced = 0;
    		//in case we replacing contracted goods, don't note replaced units on log/rating			
    		while (prevQuantity < this.$contracted[commodity] && units > 0) {
    			prevQuantity += 1;
    			units--;
    			replaced++;
    		}
    		if (replaced > 0) {
    			player.consoleMessage(replaced + " units of contracted goods replaced.");
    		}
    	}
    	if (units > 0) {
    		if (this.$buyLog[commodity]) {
    			//handle buying the same commodity multiple times
    			if (this.$buyLog[commodity][0][1] === price) {
    				this.$buyLog[commodity][0][0] += units;
    				this.$updateManifestExtras();
    				return;
    			}
    		}
    		else {
    			this.$buyLog[commodity] = new Array();
    		}
    		this.$buyLog[commodity].unshift([units, price]);
    		this.$updateManifestExtras();
    	}
    }
    
    //update purchace log
    this.playerSoldCargo = function(commodity, units, price) {
    	if (!this.$buyLog[commodity]) return;
    	var i, currentElement;
    	var tab = "";
    	var unitCount = units;
    	var profit = 0;
    	while(unitCount > 0 && this.$buyLog[commodity].length > 0) { //purge buys from log
    		currentElement = this.$buyLog[commodity].shift();
    		//There is more than enough purchases in one log entry
    		if (currentElement[0] > unitCount) {
    			profit += unitCount * (price - currentElement[1]);
    			currentElement[0] -= unitCount;
    			this.$buyLog[commodity].unshift(currentElement);
    			unitCount = 0;
    		}
    		//There is enough or less purchases in log entry
    		else {
    			profit += currentElement[0] * (price - currentElement[1]);
    			unitCount -= currentElement[0];
    		}
    	}
    	if (this.$buyLog[commodity].length === 0)
    		delete this.$buyLog[commodity];
    	//show profit if profit != 0
    	if (profit !== 0) {
    		player.consoleMessage("Profit by trade: "+formatCredits(profit/10, true, true ));
    		worldScripts["mo-traders_rating3"].$reportProfit(profit);
    	}
    	this.$updateManifestExtras();
    }
    
    
    this.shipDockedWithStation = function(station) {
    	//this is here for compatibility with commodity markets oxp. it changes system market when docked at main station
    	if (station.isMainStation) {
    		this.$sMarket = system.mainStation.market;
    	}
    	//write off lost cargo from the profit when docking
    	var profitLoss = 0;
    	for (var i in this.$buyLog) {
    		if (!manifest[i]) var unitsInCargo = 0;
    		else var unitsInCargo = manifest[i];
    		var done = false;
    		while (!done) {
    			var unitsInLog = 0;
    			//calculate total number of units in log
    			for (var j = 0; j < this.$buyLog[i].length; j++) {
    				unitsInLog += this.$buyLog[i][j][0];
    			}
    			var difference = unitsInLog - unitsInCargo;
    			//check if cargo is missing
    			if (difference > 0) {
    				//the last log entry has more than enough units to be removed, no need to remove entries
    				if (this.$buyLog[i][this.$buyLog[i].length-1][0] > difference) {
    					profitLoss += this.$buyLog[i][this.$buyLog[i].length-1][1] * difference;
    					this.$buyLog[i][this.$buyLog[i].length-1][0] -= difference;
    					done = true;
    				}
    				//the last log entry has enough or less than enough units to be removed, need to remove last entry
    				else {
    					profitLoss += this.$buyLog[i][this.$buyLog[i].length-1][1] * difference;
    					this.$buyLog[i].pop();
    				}
    			}
    			else {
    				done = true;
    				if (this.$buyLog[i].length === 0) delete this.$buyLog[i];
    			}
    		}
    	}
    	if (profitLoss > 0) {
    		worldScripts["mo-traders_rating3"].$reportProfit(-1 * profitLoss);
    		player.consoleMessage(formatCredits(profitLoss/10, true, true )+" worth of cargo is lost.");
    	}
    	this.$updateManifestExtras();
    }
    
    this.shipLaunchedFromStation = function(station) {
    	for (var i in this.$buyLog) {//sort purchase log descending
    		this.$buyLog[i].sort(function(a, b){return b[1] - a[1]});
    	}
    	this.$updateManifestExtras();
    }
    
    this.guiScreenWillChange = function(to, from) {
    	if (to === "GUI_SCREEN_MARKET") this.$updateManifestExtras();
    }
    
    //tabbing function, prefixes pads to the string
    //order true postfixes with pads
    this._tabulate = function(string, width, order) {
    	var hairSpace = String.fromCharCode(31);
    	var hairSpaceLength = defaultFont.measureString(hairSpace);
    	var currentLength = global.defaultFont.measureString(string);
    	var padsNeeded = Math.floor((width - currentLength) / hairSpaceLength);
    	if (padsNeeded >= 1)
    		var pads = new Array(padsNeeded).join(hairSpace);
    	else var pads = "";
    	if (!order) return (pads + string);
    	else return (string + pads);
    }
    
    this._percentDiff = function(avg, local, capacity) {
    	if (capacity !== 0) {
    		var percent = (local - avg) / avg * 100;
    		if (percent < -0.05) {
    			var percentText = percent.toFixed(1) + " %%";
    		}
    		else if (percent > 0.05) {
    			var percentText = "+" + percent.toFixed(1) + " %%";
    		}
    		else var percentText = "0.0 %%";
    	}
    	//no point in calculating difference for 0 prices.
    	else var percentText = "— %%";
    	return percentText;
    }
    
    this._absDiff = function(avg, local, capacity) {
    	if (capacity !== 0) {
    		var abs = (local - avg)/10;
    		if (abs < -0.05) {
    			var absText = abs.toFixed(1);
    		}
    		else if (abs > 0.05) {
    			var absText = "+" + abs.toFixed(1);
    		}
    		else var absText = "0.0";
    	}
    	//no point in calculating difference for 0 prices.
    	else var absText = "—";
    	return absText;
    }
    
    this.$updateManifestExtras = function() {
    	
    	//check what market is being shown
    	if (player.ship.docked) {
    		var sMarket = player.ship.dockedStation.market;
    	}
    	else {
    		if (player.ship.target && player.ship.target.isStation) {
    			var bcMarket = Ship.shipDataForKey(player.ship.target.dataKey)["market_broadcast"];
    			if (!bcMarket) bcMarket = true;
    			else {
    				var trues = ["1", "true", "yes"];
    				bcMarket = bcMarket.toLowerCase();
    				if (trues.indexOf(bcMarket) !== -1) bcMarket = true;
    				else bcMarket = false;
    			}
    			if (bcMarket) var sMarket = player.ship.target.market;
    			else var sMarket = this.$sMarket;
    		}
    		else var sMarket = this.$sMarket;
    	}
    	
    	var cm = worldScripts["commodity_markets"];
    	
    	for (var i in sMarket) {
    
    		//first we'll need to clean our possible earlier additions from comment
    		//there might be other content so we'll do what we can to preserve that.
    		//we're assuming (foolhardy?) that we're the last entry.
    		var cmt = manifest.comment(i);
    		if (cmt.indexOf("Purchase breakdown") !== -1) {
    			var sliceIndex = cmt.indexOf("Purchase breakdown") - 2;
    			cmt = cmt.slice(0, sliceIndex);
    			manifest.setComment(i, cmt);
    		}
    		
    		//average column
    		var line = this._tabulate(formatCredits(this.$priceData[i].average/10, true, false), 2.4);
    		
    		//differences colums
    		if (!system.isInterstellarSpace) {
    			line += this._tabulate(this._absDiff(this.$priceData[i].average, sMarket[i].price, sMarket[i].capacity), 3);
    			line += this._tabulate(this._percentDiff(this.$priceData[i].average, sMarket[i].price, sMarket[i].capacity), 4.5);
    		}
    
    		var buyColWidth = 17.5;
    		
    		//add possible commodity markets column
    		if (player.ship.docked && cm) {
    			line += this._tabulate(formatCredits(cm.$commodities[i][1]/10, true, false), 5.3);
    			buyColWidth -= 5.3;
    		}
    		
    		//add puchase log column
    		if (this.$buyLog[i]) {
    			line += this._tabulate(this.$buyLog[i][0][0] + " × ", buyColWidth); 
    			line += formatCredits(this.$buyLog[i][0][1]/10, true, false);
    			//more that one entry in log
    			if (this.$buyLog[i].length > 1) line += " >";
    			
    			//commodity details (f8-f8)
    			// 6 entries per line
    			cmt += "\n\nPurchase breakdown of held cargo: ";
    			var j = 0;
    			while (j < this.$buyLog[i].length) {
    				cmt += "\n";
    				for (var k = j; k < j + 6; k++) {
    					if (k < this.$buyLog[i].length) {
    						cmt += this._tabulate(this.$buyLog[i][k][0] + " × ", 2.6);
    						cmt += this._tabulate(formatCredits(this.$buyLog[i][k][1]/10, true, false), 2.7, true);
    					}
    					else break;
    				}
    				j += 6;
    			}
    			manifest.setComment(i, cmt);
    		}
    		
    		manifest.setShortComment(i, line);
    	}
    }
    
    Scripts/mo_traders_rating.js
    "use strict";
    
    this.name           = "mo-traders_rating3";
    this.author         = "spara";
    this.description    = "Gives the player a Trader's Rating based on profit made.";
    
    //Public function for reporting profit
    this.$reportProfit = function(profit) {
    	this.$profit += profit;
    	this.$setInstruction();
    }
    
    this.startUp = function() {
    	if (!missionVariables.marketObserverProfit) this.$profit = 0;
    	else this.$profit = missionVariables.marketObserverProfit;
    	this.$setInstruction();
    }
    
    this.playerWillSaveGame = function () {
    	missionVariables.marketObserverProfit = this.$profit;
    }
    
    this.$traderRank = function() { //return trader rank string
    	var traderRatings =[0, 1500, 5000, 20000, 50000, 100000, 250000, 500000, 1000000];
    	var i;
    	var traderRating = -1;
    	for (i = 0; i < 9; i++) {
    		if (this.$profit/10 >= traderRatings[i])
    			var traderRating = i;
    	}
    	if (traderRating != -1)
    		return expandDescription("[mo_trade_rating_" + traderRating+"]");
    	else
    		return expandDescription("[mo_trade_rating_minus]");
    }
    
    //Trader rank for market hud
    this.$setInstruction = function() { //set trader rank text to manifest
    	var traderRank = "Trader's Rating: " + this.$traderRank() + " (" + formatCredits(this.$profit/10, true, true ) + ")";
    	player.ship.setCustomHUDDial("marketObserverProfit", traderRank);
    	mission.setInstructions(traderRank+".", this.name);
    }