Back to Index | Page generated: Dec 20, 2024, 7:22:09 AM |
from Expansion Manager's OXP list | from Expansion Manifest | |
---|---|---|
Description | Provides a common interface to market scripts for OXP's to utilise, allowing for easier integrations and preventing conflicts. | Provides a common interface to market scripts for OXP's to utilise, allowing for easier integrations and preventing conflicts. |
Identifier | oolite.oxp.phkb.MarketScriptInterface | oolite.oxp.phkb.MarketScriptInterface |
Title | Market Script Interface | Market Script Interface |
Category | Miscellaneous | Miscellaneous |
Author | phkb | phkb |
Version | 1.1 | 1.1 |
Tags | ||
Required Oolite Version | ||
Maximum Oolite Version | ||
Required Expansions | ||
Optional Expansions | ||
Conflict Expansions | ||
Information URL | https://wiki.alioth.net/index.php/Market_Script_Interface | n/a |
Download URL | https://wiki.alioth.net/img_auth.php/d/d8/MarketScriptInterface_1.1.oxz | n/a |
License | CC-BY-SA-NC 4.0 | CC-BY-SA-NC 4.0 |
File Size | n/a | |
Upload date | 1700441819 |
Also read http://wiki.alioth.net/index.php/Market%20Script%20Interface
Market Script Interface ======================= By phkb Overview ======== This OXP aims to help OXP developers integrate their market scripts with other OXP's without conflict. The difficulty faced previously is that, if one OXP uses planetinfo.plist to define a market script, no other OXP can define their own market script for the same location. There is no way to integrate the two market scripts - one will win the battle of coming in last when Oolite loads and merges all the planetinfo data, and one will lose, and there is no way to know which one it will be. The Market Script Interface provides a simple way for OXP's to register their market script callbacks, and each one will be called in turn, with the "goodCommodity" being passed from one script to the next for updating. All conflicts might not be able to be avoided (for instance, if one OXP decides to zero out the quantity of a commodity, and another one making it max out), but this OXP at least provides a way to allow both to co-exist, and potentially any remaining conflict issues can be resolved by careful coding. Please note: This system will not make one OXP compatible with another if both OXP are completely rewriting values. In those instances, the OXP that runs last will be the one that gets the final say on the values being changed. There is still plenty of scope for incompatibilities. What this system does do, however, is allow OXP's that are only mildly adjusting values (for instance, boosting a value by a percentage) to work with each other without breaking the other. Usage ===== How you make use of this utility will depend on where you are currently defining your market script. trade-Goods.plist equivalent ---------------------------- If you want to control existing commodities at the commodity level, you would normally use the "market_script" property in the trade-goods.plist file. If this is preferred, use the following method: In your startUp script, add this: this.startUp = function() { var msi = worldScripts.MarketScriptInterface_Main; msi.$addMarketInterface("tradegoods_general", "_my_updateGeneralCommodityDef", this.name); } Then create a function in your worldScript file (the function must exist in a worldscript). this._my_updateGeneralCommodityDef = function(goodDefinition, station, systemID) { // perform functions on the "goodDefinition" object as required // note: you need to check whether systemID or station are available // if the systemID is available but station is null - this is a system-level change // if the station is not null, this is a station level change // if neither the station or systemID is defined, this is a global change. // note: the station and systemID may not be available to use - take care when referencing them if (!systemID && !station) { ... do things to commodity at commodity level ... } // then return the updated object in the return call return goodDefinition; } planetinfo.plist equivalent --------------------------- If you want to control commodities at a system level, you would normally use the "market_script" property on the system in the planetinfo.plist file. If this is preferred, use the following method: In your startuUp script, add this: this.startUp = function() { var msi = worldScripts.MarketScriptInterface_Main; msi.$addMarketInterface("system_local", "_my_updateLocalCommodityDef", this.name); } Then create a function in your worldScript file (the function must exist in a worldscript). this._my_updateLocalCommodityDef = function(goodDefinition, station, systemID) { // perform functions on the "goodDefinition" object as required // note: the station and systemID may not be available to use - take care when referencing them // if you only want your changes to apply to particular systems, you need to include a check for that system before making changes if (systemID && !station && systemID == x && galaxyNumber == xx) { ... do things to commodity definition... } // then return the updated object in the return call return goodDefinition; } shipdata.plist equivalent ------------------------- If you want to control commodities at a station level, you would normally use the "market_script" property on the station in the shipdata.plist file. If this is preferred, use the following method: In your shipdata.plist file (or shipdata-overrides.plist) add this: { "my_station_def" = { market_script = "msi_station_market.js"; // it must refer to this specific script }; } Then, in your startUp script, add this: this.startUp = function() { var msi = worldScripts.MarketScriptInterface_Main; msi.$addMarketInterface("station_local", "_my_updateLocalCommodityDef", this.name); } Then create a function in your worldScript file (the function must exist in a worldscript). this._my_updateLocalCommodityDef = function(goodDefinition, station, systemID) { // perform functions on the "goodDefinition" object as required // note: the station and systemID may not be available to use - take care when referencing them if (station && station.dataKey == "my_station_key") { // use whatever parameters are appropriate for identifying your station .. do things to commodity at a station level ... } // then return the updated object in the return call return goodDefinition; } Although there is usually not as much market script conflict at the station level, this method will allow multiple callers to make adjustments relatively cleanly. Adding new commodities ---------------------- If you are creating a new commodity, and want to make use of this system, you need to do the following: In your trade-goods.plist file, add the following to the new commodity definition: { "coffee" = { ... various settings... market_script = "msi_tradegoods_market.js"; // it must refer to this specific script }; } You can then follow the instructions above for interfacing at the commodity level. However, remember that the script will be called for every commodity, not just your new one. So remember to check for your commodity key before adjusting prices. For example: if (goodDefinition.key == "coffee") { ... do things to the price/quantity ... } return goodDefinition; Setting priority ================ It is possible to prioritize where your script will be called if there are more than one script to run. In your code to register your callback, you can specify a priority: // this setting will call your script before any others msi.$addMarketInterface("system_local", "_my_updateLocalCommodityDef", this.name, "highest"); // this setting will call your script after all others msi.$addMarketInterface("system_local", "_my_updateLocalCommodityDef", this.name, "lowest"); Anything without a specific priority is considered "normal" priority, and will be executed between the highest and lowest priority items. There is no way to control the priority if more than one script specifies the same priority. So, if two scripts set the "highest" priority, there is no way to predict which one will be run first. General Information =================== The market_script property of the "universal" section of planetinfo.plist will only engage if there is no market_script defined at the system level. Because we are specifying a market script for every system in every galaxy, it will never get called. If you want your script to act in a universal way, use the system-level facility provided by this interface. Market scripts defined through the "interstellar space" section of planetinfo.plist do not function. Individual station market scripts can be defined, however. Important Note ============== This system will likely break any OXP that currently defines a market script at the universal level, or potentially at the system level. Station-level markets should be unaffected. The OXP will need to be made compatible before the desired market changes are observed. License ======= 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/ Version History =============== 1.1 - Better handling of saved data, ensuring it is available as early as possible. - Added try/catch block when executing callbacks, for better protection if any callback fails. - Extra documentation around normal priority items. 1.0 - Initial release.
Path | |
---|---|
Scripts/msi_main.js | "use strict"; this.name = "MarketScriptInterface_Main"; this.author = "phkb"; this.description = "Script for allowing cooperation between market_scripts via the trade-goods.plist, planetinfo.plist and shipdata.plist files."; this.licence = "CC BY-NC-SA 4.0"; this._data = { "tradegoods_general": [], "system_local": [], "station_local": [] }; this._loaded = false; this._cleanup = false; /* with a market script defined for every system, and for every commodity, the only scripts that will be called are: tradegoods general system local station local the universal script will never be called - the system script blows it away */ //------------------------------------------------------------------------------------------------------------- this.startUp = function() { if (missionVariables.MarketScriptInterface_Data) { this.$loadData(); } } //------------------------------------------------------------------------------------------------------------- this.playerWillSaveGame = function() { this.$saveData(); } //------------------------------------------------------------------------------------------------------------- this.systemWillPopulate = function() { if (missionVariables.MarketScriptInterface_Data) { this.$loadData(); this._loaded = true; } } //------------------------------------------------------------------------------------------------------------- this.guiScreenChanged = function(to, from) { if (from == "GUI_SCREEN_OPTIONS" && this._cleanup) { if (missionVariables.MarketScriptInterface_Data) delete missionVariables.MarketScriptInterface_Data; this._cleanup = false; } } //------------------------------------------------------------------------------------------------------------- this.$loadData = function() { // make sure we only come here once if (this._loaded == true) return; if (!missionVariables.MarketScriptInterface_Data) return; if (missionVariables.MarketScriptInterface_Data) { this._data = JSON.parse(missionVariables.MarketScriptInterface_Data); delete missionVariables.MarketScriptInterface_Data; this._loaded = true; } } //------------------------------------------------------------------------------------------------------------- this.$saveData = function() { missionVariables.MarketScriptInterface_Data = JSON.stringify(this._data); this._cleanup = true; } //------------------------------------------------------------------------------------------------------------- this.$validateFunctions = function() { var sections = Object.keys(this._data); var i = sections.length; while (i--) { var callbacks = this._data[sections[i]]; var j = callbacks.length; while (j--) { var cb = callbacks[j]; // oops! function/ws combo not found if (!worldScripts[cb.worldscript][cb.functionname]) { callbacks.splice(j, 1); } } } } //------------------------------------------------------------------------------------------------------------- // need addition interfaces this.$addMarketInterface = function(section, fnName, ws, priority) { var avail = ["tradegoods_general", "system_local", "station_local"]; if (avail.indexOf(section) == -1) { log(this.name, "Exception! Invalid section defined. Must be either 'tradegoods_general', 'system_local' or 'station_local' - tried to use " + section); return false; } if (!worldScripts[ws][fnName]) { log(this.name, "Exception! WorldScript/Function not found! " + ws + "." + fnName); return false; } var callbacks = this._data[section]; var i = callbacks.length; var found = false; var low_start = -1; while (i--) { if (callbacks[i].functionname == fnName && callbacks[i].worldscript == ws) found = true; if (callbacks[i].priority == "lowest" && low_start == -1) low_start = i; } if (found == false) { // add high priority item to bottom (because we will always be going through list in reverse order) if (priority == "highest") { callbacks.push({functionname:fnName, worldscript:ws, priority:priority}); } else { if (low_start == -1 || priority == "lowest") // put any callbacks set to "lowest" to the top of the list callbacks.unshift({functionname:fnName, worldscript:ws, priority: priority}); else { callbacks.splice(low_start + 1, 0, {functionname:fnName, worldscript:ws, priority: priority}); } } } return true; } //------------------------------------------------------------------------------------------------------------- this.$process = function(section, goodDefinition, station, systemID) { //log("testing", "start process section " + section); if (!this._loaded) this.$loadData(); var callbacks = this._data[section]; var i = callbacks.length; while (i--) { var cb = callbacks[i]; if (worldScripts[cb.worldscript] && worldScripts[cb.worldscript][cb.functionname]) { try { goodDefinition = worldScripts[cb.worldscript][cb.functionname](goodDefinition, station, systemID); } catch (err) { log(this.name, "ERROR in " + cb.worldScript + "." + cb.functionname); log(this.name, "ERROR: " + err.message); } } } //log("testing", "end process section " + section); return goodDefinition; } // the functions below are the end-points for all our "market_script" definitions (see "*_market.js" files) //------------------------------------------------------------------------------------------------------------- this.$updateGeneralTradeGoodsMarket = function(goodDefinition, station, systemID) { goodDefinition = worldScripts.MarketScriptInterface_Main.$process("tradegoods_general", goodDefinition, station, systemID); return goodDefinition; } //------------------------------------------------------------------------------------------------------------- this.$updateLocalSystemMarket = function(goodDefinition, station, systemID) { goodDefinition = worldScripts.MarketScriptInterface_Main.$process("system_local", goodDefinition, station, systemID); return goodDefinition; } //------------------------------------------------------------------------------------------------------------- this.$updateLocalStationMarket = function(goodDefinition, station, systemID) { goodDefinition = worldScripts.MarketScriptInterface_Main.$process("station_local", goodDefinition, station, systemID); return goodDefinition; } |
Scripts/msi_station_market.js | "use strict"; this.name = "MarketScriptInterface_StationMarket"; this.author = "phkb"; this.description = "Script for adjusting markets via the shipdata.plist file."; this.licence = "CC BY-NC-SA 4.0"; //------------------------------------------------------------------------------------------------------------- this.updateLocalCommodityDefinition = function (goodDefinition, station, systemID) { //log(this.name, "station start local"); var msi = worldScripts.MarketScriptInterface_Main; if (msi) goodDefinition = msi.$updateLocalStationMarket(goodDefinition, station, systemID); //log(this.name, "station end local"); return goodDefinition; } |
Scripts/msi_system_market.js | "use strict"; this.name = "MarketScriptInterface_SystemMarket"; this.author = "phkb"; this.description = "Script for adjusting markets via the individual system entry in the planetinfo.plist file."; this.licence = "CC BY-NC-SA 4.0"; //------------------------------------------------------------------------------------------------------------- this.updateLocalCommodityDefinition = function (goodDefinition, station, systemID) { //log("testing", "system start local"); var msi = worldScripts.MarketScriptInterface_Main; if (msi) goodDefinition = msi.$updateLocalSystemMarket(goodDefinition, station, systemID); //log("testing", "system end local"); return goodDefinition; } |
Scripts/msi_tradegoods_market.js | "use strict"; this.name = "MarketScriptInterface_TradeGoodsMarket"; this.author = "phkb"; this.description = "Script for adjusting markets via the trade-goods.plist file."; this.licence = "CC BY-NC-SA 4.0"; //------------------------------------------------------------------------------------------------------------- this.updateGeneralCommodityDefinition = function (goodDefinition, station, systemID) { //log("testing", "tradegoods start general"); var msi = worldScripts.MarketScriptInterface_Main; if (msi) goodDefinition = msi.$updateGeneralTradeGoodsMarket(goodDefinition, station, systemID); //log("testing", "tradegoods end general"); return goodDefinition; } |