Back to Index Page generated: May 8, 2024, 6:16:03 AM

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
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

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);
}