Difference between revisions of "Trade-goods.plist"
Cholmondely (talk | contribs) (Added another example) |
(Updating BB links) |
||
(14 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
'''Note: this page describes functionality currently only in the 1.81 development code. It may change further before being part of a stable release.''' | '''Note: this page describes functionality currently only in the 1.81 development code. It may change further before being part of a stable release.''' | ||
− | + | == Preamble == | |
+ | This merely re-defines the trade commodities in the game (''eg'' introducing new ones, or changing elements of the vanilla game ones). See [[SW Economy]] for an example of what can be done. | ||
− | The file is a dictionary, with keys as commodity keys, and values defining the quantities, prices and other properties of those commodities. | + | The trade-goods.plist file replaces and extends the old [[commodities.plist]] and [[illegal_goods.plist]] files from Oolite 1.81 onwards to allow greater flexibility in trade good definitions, including defining entirely new goods. But it does not remove slaves from a particular station or make them cost twice as much as they do at the main orbital station. |
+ | |||
+ | To do other things, ''eg''. to tweak the pricing/availability of commodities in a particular station, you need to define the market inside that station's [[Shipdata.plist]] - or include a "hook" inside that shipdata.plist to a javascript [[Oolite_Market_Scripts|Oolite Market Script]] (which allows more complex definitions). | ||
+ | |||
+ | == Overview == | ||
+ | |||
+ | The Trade-goods.plist file is a dictionary, with keys as commodity keys, and values defining the quantities, prices and other properties of those commodities. | ||
{ | { | ||
Line 76: | Line 83: | ||
=== market_script === | === market_script === | ||
The name of a Javascript file to use as a [[Oolite Market Scripts|market script]] for this trade good when calculating main station quantities and prices. | The name of a Javascript file to use as a [[Oolite Market Scripts|market script]] for this trade good when calculating main station quantities and prices. | ||
+ | |||
+ | '''Note:''' [https://bb.oolite.space/viewtopic.php?p=235837#p235837 What's the difference between the market script specified in planetinfo.plist and the one called for by trade-goods.plist?] (2015) | ||
=== name === | === name === | ||
Line 112: | Line 121: | ||
=== short_comment === | === short_comment === | ||
− | A string that will be displayed on the F8 commodity list screen in the optional extra column. This can be modified later by [[Oolite_JavaScript_Reference:_Manifest# | + | A string that will be displayed on the F8 commodity list screen in the optional extra column. This can be modified later by [[Oolite_JavaScript_Reference:_Manifest#setShortComment|manifest.setShortComment()]]. As the meaning of this value will depend on the OXPs installed, it is unlikely to be useful to specify an initial value in the plist. |
=== sort_order === | === sort_order === | ||
Line 122: | Line 131: | ||
== Secondary Market Definitions == | == Secondary Market Definitions == | ||
+ | |||
+ | The Primary market is that of the main orbital station of the system. The Secondary markets are other station/dockable markets (which are influenced by the primary market), ''eg'' that of a local Rock Hermit, or a visiting liner or another orbital station. Both quantities and prices will be influenced by those of the primary market. Needless to say, the primary market is randomly recalculated each time the system is visited. | ||
Secondary market definitions are entered in the [[shipdata.plist#market_definition|market_definition]] key in shipdata.plist, which is an array of dictionaries, each defining rules which modify the prices and quantities from the main station market, and possibly adjust the market capacity and legality of the good. The following modifications are applied for each trade good, assuming no market_script is defined: | Secondary market definitions are entered in the [[shipdata.plist#market_definition|market_definition]] key in shipdata.plist, which is an array of dictionaries, each defining rules which modify the prices and quantities from the main station market, and possibly adjust the market capacity and legality of the good. The following modifications are applied for each trade good, assuming no market_script is defined: | ||
Line 161: | Line 172: | ||
secondary_quantity = (adjusted_quantity + quantity_adder) * (quantity_multiplier + (quantity_randomiser * rnd))) | secondary_quantity = (adjusted_quantity + quantity_adder) * (quantity_multiplier + (quantity_randomiser * rnd))) | ||
As an exception, if quantity_adder and quantity_multiplier are both <= 0, then the result is always 0 regardless of quantity_random. | As an exception, if quantity_adder and quantity_multiplier are both <= 0, then the result is always 0 regardless of quantity_random. | ||
+ | |||
+ | === Links === | ||
+ | *[https://bb.oolite.space/viewtopic.php?f=2&t=21512 Secondary Market Definitions Defined] (2023) | ||
== Examples == | == Examples == | ||
− | Hardly anybody ended up using this! And cim never even put it into his [[New Cargoes]] oxp! Spara wrote a conversion script for the old commodities.plist, and everybody seems to have used that instead. | + | Hardly anybody ended up using this! And cim never even put it into his [[New Cargoes]] oxp! Spara wrote a conversion script for the old commodities.plist, and everybody seems to have used that instead for determining markets for new stations. |
− | === From the | + | === From the vanilla game: Rock Hermits === |
− | + | The "market_definition" from the shipdata.plist in the config folder | |
"oolite_template_rock-hermit" = | "oolite_template_rock-hermit" = | ||
Line 222: | Line 236: | ||
.... | .... | ||
}; | }; | ||
− | === From Cim's oxp: Risk-based Economy (v.2.0 | + | |
+ | === From Cim's oxp: Risk-based Economy (v.2.0: the Trade-goods.plist updated from the original commodities.plist) === | ||
{ | { | ||
"food" = { | "food" = { | ||
Line 271: | Line 286: | ||
}; | }; | ||
+ | } | ||
+ | === A new commodity (from the Trade-goods.plist inside "SW Economy") === | ||
+ | "medicine" = { | ||
+ | "name" = "[commodity-name medicine]"; | ||
+ | "classes" = ("oolite-consumer","oolite-medical"); | ||
+ | "quantity_unit" = 1; | ||
+ | "peak_export" = 7; | ||
+ | "peak_import" = 0; | ||
+ | "price_average" = 640; | ||
+ | "price_economic" = 0.25; | ||
+ | "price_random" = 0.15; | ||
+ | "quantity_average" = 5; | ||
+ | "quantity_economic" = 1.5; | ||
+ | "quantity_random" = 1.0; | ||
+ | "legality_export" = 0; | ||
+ | "legality_import" = 0; | ||
+ | "trumble_opinion" = 0.50; | ||
+ | "sort_order" = 500; | ||
+ | "comment" = "[oolite-commodity-medicine]"; | ||
+ | }; | ||
+ | |||
+ | === Setting an orbital main station's slaves on sale to zero === | ||
+ | [[User:Phkb|Phkb]] provided this example which requires a ''planetinfo.plist'' & a new ''.js file'' - there are other ways to achieve the same result | ||
+ | ==== 1) planetinfo.plist (to invoke the market script) ==== | ||
+ | (in the "Config" folder) | ||
+ | |||
+ | { | ||
+ | "0 96" = {market_script = "digebiti_noslaves.js";}; | ||
+ | } | ||
+ | |||
+ | ==== 2) Market script: digebiti_noslaves.js ==== | ||
+ | (in the "Scripts" folder) - note the complex name, which prevents confusion with other snippets of script | ||
+ | |||
+ | "use strict"; | ||
+ | this.name = "digebiti_noslaves"; | ||
+ | this.author = "Phkb"; | ||
+ | this.copyright = "(C) 2022 Phkb"; | ||
+ | this.licence = "CC-NC-by-SA 4.0"; | ||
+ | this.description = "Removes slaves for sale in Digebiti"; | ||
+ | this.version = "1.0"; | ||
+ | |||
+ | this.updateLocalCommodityDefinition = function(goodDefinition, station, systemID) { | ||
+ | if (goodDefinition.key == "slaves" && station && station.isMainStation) { | ||
+ | goodDefinition.quantity = 0; | ||
+ | } | ||
+ | return goodDefinition; | ||
+ | } | ||
+ | |||
+ | This method will work, so long as no other OXP changes the market_script property of the Digebiti system in the same way. | ||
+ | |||
+ | === Creating an invariant market independent of the local system economy (agricultural/industrial) === | ||
+ | If you just use a market_definition in shipdata.plist for the station, then you're merely setting variations on the primary system market. | ||
+ | |||
+ | If you want the station prices to be independent of (or more loosely tied to) the system it's in, then you need to use a [[Oolite_Market_Scripts|market script]]. | ||
+ | |||
+ | |||
+ | So what you'd have is something like this - let's say it's a hydroponics station, so it always sells food and nothing else, regardless of whether the general system economy is industrial or agricultural: | ||
+ | |||
+ | // this is going to get called once for each trade good on the market | ||
+ | function updateLocalCommodityDefinition (goodDefinition, station, system) { | ||
+ | // at this point, goodDefinition will contain the trade-goods.plist information on the commodity itself | ||
+ | // and a price and quantity key to say what the processing so far has done in mutating the default system market | ||
+ | // so that might say "price" = 35; "quantity" = 50; | ||
+ | // the important thing is that we can just ignore that entirely if we want | ||
+ | if (goodDefinition.key == "food") { | ||
+ | // are we calculating the basic Oolite "food" trade good right now? If yes, set our own static prices | ||
+ | goodDefinition.price = 25; // 2.5 credits | ||
+ | goodDefinition.quantity = 80; // lots of food | ||
+ | } else { | ||
+ | // if not, don't buy or sell any other commodity | ||
+ | // TODO: work out what we should import | ||
+ | goodDefinition.price = 0; | ||
+ | goodDefinition.quantity = 0; | ||
+ | goodDefinition.capacity = 0; | ||
+ | } | ||
+ | return goodDefinition; | ||
} | } | ||
+ | |||
+ | Now ... the reason that market definitions are preferred to be mutators of the system economy is for two reasons. | ||
+ | 1) it means you can avoid the situation where you end up with two adjacent stations with wildly different prices ... but you can of course handle that in other ways too, station positioning, restricted access, ''etc''. | ||
+ | 2) it makes it easier to handle OXP trade goods: | ||
+ | - other OXPs might have added more "food" type goods, ''e.g.'' "Meat" or "Cheese" | ||
+ | - you can learn about those and handle them specifically | ||
+ | |||
+ | } else if (goodDefinition.key == "bobsfoodoxp_cheese") { | ||
+ | // set price and quantity here | ||
+ | |||
+ | ... but that means a lot of keeping up with what everyone else is doing with custom goods. | ||
+ | |||
+ | The idea was that for your hydroponics station what you might do instead is something like this | ||
+ | |||
+ | // this is going to get called once for each trade good on the market | ||
+ | function updateLocalCommodityDefinition (goodDefinition, station, system) { | ||
+ | // at this point, goodDefinition will contain the trade-goods.plist information on the commodity itself | ||
+ | // and a price and quantity key to say what the processing so far has done in mutating the default system market | ||
+ | // so that might say "price" = 35; "quantity" = 50; | ||
+ | // the important thing is that we can just ignore that entirely if we want | ||
+ | if (goodDefinition.classes.indexOf("oolite-edible") >= 0) { | ||
+ | // has this commodity been declared either by Oolite Core or by the OXP that added it as | ||
+ | // some sort of food? | ||
+ | goodDefinition.price = goodDefinition.price * 0.6; // big discount for buying at source | ||
+ | goodDefinition.quantity = 10 + (goodDefinition.quantity * 2); // lots of it, and at least 10t even in heavily industrial systems | ||
+ | } else { | ||
+ | // if not, don't buy or sell any other commodity | ||
+ | // TODO: work out what we should import | ||
+ | goodDefinition.price = 0; | ||
+ | goodDefinition.quantity = 0; | ||
+ | goodDefinition.capacity = 0; | ||
+ | } | ||
+ | return goodDefinition; | ||
+ | } | ||
+ | |||
+ | So now if another OXP adds Cheese, provided they declare it as "edible", your station will sell it in high quantities at a big discount, but you don't need to know exactly what the baseline price or quantity was set - so if a food-focused OXP was to add "Wine", "Fine Wine" and "Vintage Wine" at a range of prices and quantities, you'd be automatically handling those in reasonable line with a combination of the Wine OXP's basic pricing structure and your station OXP's "food primary source market" style, without necessarily needing to know about every future Wine OXP there might be and explicitly set compatibility. | ||
+ | |||
+ | Note in this example we're still zeroing out all non-food goods - OXP included! - regardless of the local economy. Maybe at this point we'd want to include and document a "hydroponicsoxp-agriculturalequipment" trade good class that other OXPs could use, which this station would buy up at higher demand and prices than normal. | ||
+ | |||
+ | // this is going to get called once for each trade good on the market | ||
+ | function updateLocalCommodityDefinition (goodDefinition, station, system) { | ||
+ | // at this point, goodDefinition will contain the trade-goods.plist information on the commodity itself | ||
+ | // and a price and quantity key to say what the processing so far has done in mutating the default system market | ||
+ | // so that might say "price" = 35; "quantity" = 50; | ||
+ | // the important thing is that we can just ignore that entirely if we want | ||
+ | if (goodDefinition.classes.indexOf("oolite-edible") >= 0) { | ||
+ | // has this commodity been declared either by Oolite Core or by the OXP that added it as | ||
+ | // some sort of food? | ||
+ | goodDefinition.price = goodDefinition.price * 0.6; // big discount for buying at source | ||
+ | goodDefinition.quantity = 10 + (goodDefinition.quantity * 2); // lots of it, and at least 10t even in heavily industrial systems | ||
+ | } else if (goodDefinition.classes.indexOf("hydroponicsoxp-agriculturalequipment") >= 0) { | ||
+ | // custom class for other OXPs to add specific agricultural equipment e.g. combine harvesters, compost, fertiliser | ||
+ | goodDefinition.price = goodDefinition.price * 1.3; // better prices here | ||
+ | goodDefinition.capacity = 16; // buy a small amount only // TODO: raise capacity if price per tonne is low | ||
+ | goodDefinition.quantity = 0; // not selling this | ||
+ | } else if (goodDefinition.key == "machinery") { | ||
+ | // the basic Oolite Machinery commodity isn't in the custom class but we need to buy something if | ||
+ | // no OXPs adding it are installed | ||
+ | goodDefinition.price = goodDefinition.price * 1.3; // better prices here | ||
+ | goodDefinition.quantity = 0; // not selling | ||
+ | goodDefinition.capacity = 16; // small capacity to buy | ||
+ | } else { | ||
+ | // don't buy or sell any other commodity | ||
+ | goodDefinition.price = 0; | ||
+ | goodDefinition.quantity = 0; | ||
+ | goodDefinition.capacity = 0; | ||
+ | } | ||
+ | return goodDefinition; | ||
+ | } | ||
+ | |||
+ | So you can see this one has a mix of using, mutating and overruling the definitions set by the system market and OXP commodity definitions ... with this method you could have a whole bunch of OXPs setting up themed stations, and a whole different bunch adding their own custom commodities, and they'd barely have to know about each other to ensure reasonable consistency and compatibility from someone installing any combination of them. (- by [[User:Cim|Cim]], 2023) | ||
+ | |||
+ | Note: this gives examples for 1 commodity at a time, but not how to easily do that for ALL the commodities on ALL the extra stations in a system. | ||
+ | |||
+ | == Using the new Trade-goods.plist == | ||
+ | The original commodities.plist was really not very good - it was based very strongly on the original Elite 84 pricing algorithm, which was written to use peculiarities of 8-bit Maths to its advantage. So one ended up with a series of fairly incomprehensible integers defining each market. | ||
+ | |||
+ | The trade-goods.plist was designed to make that simpler, by: | ||
+ | :1) Making it a dictionary rather than an unlabelled array so one could tell which parameter was which | ||
+ | :2) Making the price range for a trade good dependent on more sensible variables, so rather than setting the price range with a bitmask, one set the price and quantity variations dependent on the economy and on random chance. | ||
+ | |||
+ | Compare the v.1.81 trade-goods.plist with a v.1.80 commodities.plist - they were designed to give near-identical market behaviour for the built-in trade goods. | ||
+ | |||
+ | But - that only made it more comprehensible. It didn't really let one do anything particularly different to Elite 84 markets - mostly including the "Rich Industrial -> Poor Agricultural" single spectrum. One can tweak that a bit with peak_export and peak_import to get slightly different shapes (so a good might peak at Poor Industrial rather than Rich Industrial) but it doesn't really give one the option of a third economy type still. | ||
+ | |||
+ | ''So how does one introduce a new economic pole, such as a mining industry?'' | ||
+ | |||
+ | For that, one needs the scripting support. [[Oolite JavaScript Reference: Market Scripts]]. <br> | ||
+ | One can quite literally do whatever one likes with the prices that way. One could have every *system* have its own separate economy, which varied over time according to events, what the player had previously traded, ''etc. etc.'' to produce something comparable to Elite Dangerous' economic sim, if that's the route one wanted to go down. Obviously the more complexity one adds the harder it gets to script up, but it's now all possible. | ||
+ | |||
+ | [[File:SOTL Altmap F8.png|thumb|right|200px|SOTL Altmap F8 commodities market]] | ||
+ | [[SOTL Altmap]] does have an example of it, which basically throws out the original Elite-like algorithm of trade-goods.plist and starts over. It goes *way* beyond just adding a third economy, too. | ||
+ | |||
+ | Basically in SOTL every trade good is tagged in the trade-goods.plist with a bunch of classes | ||
+ | ''e.g.'' | ||
+ | classes = ("sotl-ex-salvage","sotl-im-refining","sotl-quantity-high","sotl-priceband-3","sotl-demand-medium","sotl-supply-medium","sotl-volatility-low"); | ||
+ | So that means: | ||
+ | - exported by salvage economies | ||
+ | - imported by refining economies | ||
+ | - high quantity, moderate price, low price volatility | ||
+ | |||
+ | Then the market script uses those classes, rather than the standard pricing variables, to define the economic behaviour. | ||
+ | :So line 69-74ish: get the current economy description, convert that to the format used in the class name | ||
+ | :...line 85-90: check for the import and export classes for that economy and see if it's imported or exported | ||
+ | :...lines 100-120: apply various special conditions, so e.g. the sotl-ims-radiation class checks the planet surface radiation levels (custom property in sysinfo) and if the radiation level is high, imports these goods even if the basic economy isn't interested (e.g. "sotl-ceramics-ind" in the plist) | ||
+ | :...and then it uses those calculations (and a bunch more of the classes) in the rest of the script to set the price and quantity depending on whether it's an import, an export, or neither | ||
+ | |||
+ | That's massive overkill for just adding a third mining economy, but one would need to work on a similar principle. | ||
+ | |||
+ | :1) Tag every commodity with classes for (import|export|none)-(agricultural|industrial|mining) as appropriate | ||
+ | :2) Adjust the economy names (there are a few different ways one could do that) so that the 8 economy IDs were allocated to agricultural, industrial and mining economies | ||
+ | :3) Have a price script which like the SOTL one gets the current economy, looks at the classes list to find out if the commodity is imported or exported or neither, and sets the price and quantity accordingly. One could use the standard price_economic, price_average, price_random properties in the plist to give baseline data to the script if one was not planning to do anything too weird. | ||
+ | |||
+ | {Technically one doesn't need the classes - just have the price script contain a list of goods internally for each economy - but this way if other people add extra trade goods in their own OXPs, they can tag them up for mining economy support too} | ||
+ | |||
+ | All fairly straightforward theoretically. | ||
+ | |||
+ | ''The biggest impediment to doing a wholesale reworking of the economic system, is just how incompatible it makes lots and lots of OXP's. In order to override the vanilla commodities as in SOTL, requires setting a script for each in trade-goods.plist. Which is fine if it's the only OXP that wants to play around with commodity prices. As soon as two OXP's try to do the same thing, one gets a conflict and no longer knows which OXP pricing model is in play.'' | ||
+ | |||
+ | ''For SOTL it's fine, because it uses the scenario system to forcibly eject all other OXP's from use while it's running. But if one wants the OXP to go in the general playing pool set of OXP's, it becomes much more complicated. [[User:Phkb|Phkb]] spent a long time trying to work out why his market script wasn't running, only to find that [[UPS Courier]] was quietly adjusting the markets in certain systems via the planetinfo.plist file. And there are a lot of OXP's that want to play in this space, tweaking prices up or down.'' | ||
+ | |||
+ | ''Hence one usually abandons using any of the script options, and just tweaks the price after all the scripts have finished playing, ''eg'' in [[Smugglers]]. It was the only way to guarantee getting some sort of bump in a price when wanted.'' | ||
== Links == | == Links == | ||
− | *[[Commodities.plist]] - the precursor (which still mostly works!). | + | *[[Oolite JavaScript Reference: Market Scripts]] |
− | *[ | + | *[[Commodities.plist]] - the precursor (which still mostly works!). If it doesn't see [https://bb.oolite.space/viewtopic.php?p=246145#p246145 here] (2015) |
− | *[ | + | *[https://bb.oolite.space/viewtopic.php?f=2&t=16828 Proposal for 1.82: support for economic changes] ([[User:Cim|Cim]], 2014) |
+ | *[https://bb.oolite.space/viewtopic.php?f=2&t=16735 Cim on introducing the Trade-goods.plist and deprecating the commodities.plist] (2014) | ||
{{QuoteText|Text=Trade goods work is coming along fairly well. As well as plist-coded prices for trade goods, which should handle the simple cases (including a few things not possible with commodities.plist), you can now specify a "market script" which allows you to set prices per trade-good, per station, or per system. If you try to do all three, the following happens: <br> | {{QuoteText|Text=Trade goods work is coming along fairly well. As well as plist-coded prices for trade goods, which should handle the simple cases (including a few things not possible with commodities.plist), you can now specify a "market script" which allows you to set prices per trade-good, per station, or per system. If you try to do all three, the following happens: <br> | ||
1) The per-trade good script sets a standard price and quantity for the system's economy and any other data it considers <br> | 1) The per-trade good script sets a standard price and quantity for the system's economy and any other data it considers <br> | ||
Line 283: | Line 497: | ||
4) The per-trade good script runs again in "secondary station" mode, to modify the price and quantity again <br> | 4) The per-trade good script runs again in "secondary station" mode, to modify the price and quantity again <br> | ||
5) The per-station script runs to make final adjustments to price and quantity <br> | 5) The per-station script runs to make final adjustments to price and quantity <br> | ||
− | Generally one OXP wouldn't do all three, though.|Source=([ | + | Generally one OXP wouldn't do all three, though.|Source=([https://bb.oolite.space/viewtopic.php?p=226722#p226722 Cim in Progress Thread (2014)])}} |
+ | === Using the Trade-goods.plist === | ||
+ | *[https://bb.oolite.space/viewtopic.php?f=4&t=20576 How to get the displayName for a commodity in the Main Station Market?] (2020) | ||
+ | *[https://bb.oolite.space/viewtopic.php?f=6&t=17269 Thinking about Planet Income classification] (2015) | ||
+ | |||
+ | {{QuoteText|Text= | ||
+ | In 1.81 this is a lot easier to OXP - you can introduce as many economies as you like, though you are limited to eight economy symbols (so if you wanted lots of economy types, you might end up with Poor Industrial, Avg Industrial and Rich Industrial sharing a symbol on the map). The trade good pricing scripts can then be used to set up different price categories in different economies (and introduce additional trade goods, or split an existing good into multiple categories, which may be necessary if you're adding more economies). | ||
+ | Resetting economies across the planets is a big change in terms of lines of code, but is also very easy to automate. If OXPers are looking for a scripting tool to use for this sort of automation, then NodeJS or io.js is free and also uses Javascript as its language, so you don't need to learn a new one - and may even be able to share code between your build scripts and your OXP, though I haven't tried that yet.|Source=([https://bb.oolite.space/viewtopic.php?f=6&t=17269 Cim in Planet Income thread (2015)])}} | ||
[[Category:Oolite]] | [[Category:Oolite]] | ||
[[Category:Oolite scripting]] | [[Category:Oolite scripting]] |
Latest revision as of 03:37, 29 February 2024
Note: this page describes functionality currently only in the 1.81 development code. It may change further before being part of a stable release.
Contents
- 1 Preamble
- 2 Overview
- 3 Property keys
- 3.1 capacity
- 3.2 classes
- 3.3 comment
- 3.4 legality_export
- 3.5 legality_import
- 3.6 market_script
- 3.7 name
- 3.8 peak_export
- 3.9 peak_import
- 3.10 price_average
- 3.11 price_economic
- 3.12 price_random
- 3.13 quantity_average
- 3.14 quantity_economic
- 3.15 quantity_random
- 3.16 quantity_unit
- 3.17 short_comment
- 3.18 sort_order
- 3.19 trumble_opinion
- 4 Secondary Market Definitions
- 5 Examples
- 5.1 From the vanilla game: Rock Hermits
- 5.2 From Cim's oxp: Risk-based Economy (v.2.0: the Trade-goods.plist updated from the original commodities.plist)
- 5.3 A new commodity (from the Trade-goods.plist inside "SW Economy")
- 5.4 Setting an orbital main station's slaves on sale to zero
- 5.5 Creating an invariant market independent of the local system economy (agricultural/industrial)
- 6 Using the new Trade-goods.plist
- 7 Links
Preamble
This merely re-defines the trade commodities in the game (eg introducing new ones, or changing elements of the vanilla game ones). See SW Economy for an example of what can be done.
The trade-goods.plist file replaces and extends the old commodities.plist and illegal_goods.plist files from Oolite 1.81 onwards to allow greater flexibility in trade good definitions, including defining entirely new goods. But it does not remove slaves from a particular station or make them cost twice as much as they do at the main orbital station.
To do other things, eg. to tweak the pricing/availability of commodities in a particular station, you need to define the market inside that station's Shipdata.plist - or include a "hook" inside that shipdata.plist to a javascript Oolite Market Script (which allows more complex definitions).
Overview
The Trade-goods.plist file is a dictionary, with keys as commodity keys, and values defining the quantities, prices and other properties of those commodities.
{ "food" = { "name" = "[commodity-name food]"; "classes" = ("oolite-consumer","oolite-edible","oolite-farming"); "quantity_unit" = 0; // 0=t "peak_export" = 7; // Poor Ag "peak_import" = 0; // Rich Ind "price_average" = 50; // decicredits // fraction of average ~= 2.75 credits "price_economic" = 0.55; // fraction of average ~= 0.2 credits "price_random" = 0.04; "quantity_average" = 13.5; // gets rounded "quantity_economic" = 0.52; "quantity_random" = 0.04; "legality_export" = 0; "legality_import" = 0; "trumble_opinion" = 1.0; "sort_order" = 100; }; }
The following keys are accepted within a commodity definition
Property keys
capacity
The maximum amount of this good which can be held at a main station market. The default is 127 units.
classes
An array of good class names. Good classes can be used by secondary stations to set pricing rules for groups of goods.
The following classes are defined by Oolite, with the core goods in those classes also listed. OXPs should usually add these classes as appropriate to custom trade goods they create, and may also define their own classes (which should be given an OXP-specific prefix). Some built-in classes only contain one built-in good, to allow for easier sub-typing by OXPs.
- oolite-alien: goods which were not made by a Cooperative species (Alien Items)
- oolite-animalproduct: goods which are obtained from animals but are not themselves animals (Furs)
- oolite-business: goods which are usually obtained for industrial or other corporate use (Computers, Machinery, Alloys, Minerals, Gold, Platinum)
- oolite-consumer: goods often obtained for personal use (Food, Textiles, Liquor/Wines, Luxuries, Narcotics, Furs, Gold, Platinum, Gem Stones)
- oolite-dangerous: goods which may be hazardous (Radioactives, Narcotics, Firearms)
- oolite-edible: goods which can be eaten reasonably safely (Food, Liquor/Wines)
- oolite-farming: goods which are produced by farming or similar agricultural processes (Food, Textiles, Liquor/Wines, Furs)
- oolite-living: goods which contain living creatures (Slaves)
- oolite-luxury: goods which are luxuries, usually a subset of oolite-consumer (Luxuries)
- oolite-machinery: goods which are industrial machinery (Machinery)
- oolite-medical: goods which may have a medical purpose (Narcotics)
- oolite-metals: goods which are largely or entirely made of metal (Alloys, Gold, Platinum)
- oolite-military: goods which are of interest to the military (Firearms, Alien Items)
- oolite-mining: goods which are produced by mining operations or similar extraction processes (Radioactives, Minerals, Gold, Platinum, Gem Stones)
- oolite-rawmaterials: goods which are raw materials needing refining to be used further (Minerals)
- oolite-restricted: goods which are subject to restrictions on trade (Slaves, Narcotics, Firearms)
- oolite-salvage: goods which are often retrieved from space battles (Slaves, Alloys)
- oolite-shipyard: goods used in the production of space ships (Computers, Alloys)
- oolite-slaves: goods which are slaves (Slaves)
- oolite-technological: goods requiring a high-tech process to produce (Computers, Machinery)
- oolite-thargoid: goods of Thargoid origin (Alien Items)
- oolite-weapons: goods which are weapons (Firearms)
- oolite-wearable: goods which are or can be used to make clothes (Textiles, Furs)
comment
A string that will be displayed on the F8 F8 commodity detail screen to describe the commodity. This can be modified later by manifest.setComment()
legality_export
This number will be multiplied by the number of units carried when leaving a main station (or another station which enforces Cooperative market laws). This value will be ORed with the player's bounty.
legality_import
This number will be multiplied by the number of units carried when entering a main station (or another station which enforces Cooperative market laws). This value will be ORed with the player's bounty.
No core game good is illegal to import.
market_script
The name of a Javascript file to use as a market script for this trade good when calculating main station quantities and prices.
name
A string containing the name of the trade good. This may contain []s for descriptions.plist expansion.
peak_export
A number from 0 to 7 identifying the economy which will give the best price for buying this good. Economies closer to this economy than the peak_import will also have below-average prices.
peak_import
A number from 0 to 7 identifying the economy which will give the best price for selling this good. Economies closer to this economy than the peak_export will also have above-average prices.
price_average
The average price of this trade good at a main system station which neither imports nor exports, in decicredits.
price_economic
The proportion of the price affected by the system economy. A value of 0 means that this good does not change in price depending on system economy. A value of 0.3 means that the good would be 0.7 times price_average in an ideal exporting system, and 1.3 times price_average in an ideal importing system. While values greater than 1 may be used, in general much lower values are better.
price_random
The proportion of the price affected by random factors. A value of 0.6 means that the actual price of the good in a system which neither imports nor exports may be between 0.4 and 1.6 times the price_average.
If this value is larger than price_economic, this means that even an ideal trade run from the best exporter to the best importer is not guaranteed to make a profit.
quantity_average
The average quantity of this trade good at a main system station which neither imports nor exports, in decicredits. If the calculated quantity exceeds the capacity, it will be capped.
quantity_economic
The proportion of the quantity affected by the system economy. A value of 0 means that this good does not change in quantity depending on system economy. A value of 0.3 means that the good would be 0.7 times as frequent in an ideal importing system, and 1.3 times as frequent in an ideal exporting system. A value of 1 or greater will make the good never or rarely (depending on quantity_random) available in systems which import the good.
quantity_random
The proportion of the quantity affected by random factors. A value of 0.6 means that the actual quantity of the good in a system which neither imports nor exports may be between 0.4 and 1.6 times the quantity_average.
If this value is greater than 1, then systems may often entirely lack this good. If this value is greater than 1 + quantity_economic, then even an ideal exporter may not have any in stock.
quantity_unit
The size of container one unit of this good represents. 0 = tonne; 1 = kilogram; 2 = gram. The default is zero.
short_comment
A string that will be displayed on the F8 commodity list screen in the optional extra column. This can be modified later by manifest.setShortComment(). As the meaning of this value will depend on the OXPs installed, it is unlikely to be useful to specify an initial value in the plist.
sort_order
A number positioning the item on the F8 screen when goods are sorted in the default order. The core goods have values 100 to 1700 in steps of 100.
trumble_opinion
How likely is a hungry trumble to consider eating this good? Goods with a value of 0 for this will never be eaten by trumbles; other goods may be eaten depending on the value of this parameter and the values for other goods on board.
Secondary Market Definitions
The Primary market is that of the main orbital station of the system. The Secondary markets are other station/dockable markets (which are influenced by the primary market), eg that of a local Rock Hermit, or a visiting liner or another orbital station. Both quantities and prices will be influenced by those of the primary market. Needless to say, the primary market is randomly recalculated each time the system is visited.
Secondary market definitions are entered in the market_definition key in shipdata.plist, which is an array of dictionaries, each defining rules which modify the prices and quantities from the main station market, and possibly adjust the market capacity and legality of the good. The following modifications are applied for each trade good, assuming no market_script is defined:
- The market definition array is searched from top to bottom for the first element matching the trade good
- The market quantity is scaled in proportion to the relative capacities for that good in the two markets.
- Otherwise, the price and quantity modifications defined in the plist are used.
Secondary Market Matching
This uses the 'type' and 'name' properties. Type has three possible values
- type = 'good': the good with the key in trade-goods.plist exactly matching the name property
- type = 'class': any good in trade-goods.plist with an entry in its classes list exactly matching the name property
- type = 'default': any good. The name property is ignored if present.
'default' should generally only be used for the last entry in the market definition.
Secondary Market Modifications
The following keys in the chosen market definition are then used to modify the good
capacity
The market capacity for this good or set of goods. If omitted, the default capacity for the station will be used.
legality_import, legality_export
If present, these keys override the keys of the same name in the basic trade good definition. If omitted, the station will use the same rules as the main station, provided that its market is monitored at all
price_adder, price_multiplier, price_randomiser
These keys modify the price in the secondary market. The formula is (in decicredits):
rnd = -1..1 // random number in range -1 to +1, slightly biased towards zero secondary_price = MAX(1, (primary_price + price_adder) * (price_multiplier + (price_randomiser * rnd)))
As an exception, if price_adder and price_multiplier are both <= 0, then the result is always 0 regardless of price_random.
quantity_adder, quantity_multiplier, quantity_randomiser
These keys together with the capacity modify the quantity in the secondary market. The formula is:
rnd = -1..1 // random number in range -1 to +1, slightly biased towards zero adjusted_quantity = (local_capacity / primary_capacity) * primary_quantity; secondary_quantity = (adjusted_quantity + quantity_adder) * (quantity_multiplier + (quantity_randomiser * rnd)))
As an exception, if quantity_adder and quantity_multiplier are both <= 0, then the result is always 0 regardless of quantity_random.
Links
Examples
Hardly anybody ended up using this! And cim never even put it into his New Cargoes oxp! Spara wrote a conversion script for the old commodities.plist, and everybody seems to have used that instead for determining markets for new stations.
From the vanilla game: Rock Hermits
The "market_definition" from the shipdata.plist in the config folder
"oolite_template_rock-hermit" = { .... is_carrier = 1; is_template = 1; "market_capacity" = 31; // maximum capacity for any good "market_definition" = ( { // export cheap mining products "type" = "class"; "name" = "oolite-mining"; "price_multiplier" = 0.8; "price_randomiser" = 0.1; "quantity_multiplier" = 3.5; "quantity_randomiser" = 3.0; }, { // import supplies a bit "type" = "class"; "name" = "oolite-edible"; "price_multiplier" = 1.05; "price_randomiser" = 0.05; "quantity_multiplier" = 0.0; "capacity" = 15; }, { // sometimes need clothes, but usually have enough already "type" = "class"; "name" = "oolite-wearable"; "price_multiplier" = 0.8; "price_randomiser" = 0.4; "quantity_multiplier" = 0.0; "capacity" = 7; }, { // sometimes need new mining equipment "type" = "class"; "name" = "oolite-machinery"; "price_multiplier" = 0.8; "price_randomiser" = 0.4; "quantity_multiplier" = 0.0; "capacity" = 7; }, { // not really interested "type" = "default"; "price_multiplier" = 0.55; "price_randomiser" = 0.25; "quantity_multiplier" = 0.0; "capacity" = 3; } ); "market_monitored" = no; .... };
From Cim's oxp: Risk-based Economy (v.2.0: the Trade-goods.plist updated from the original commodities.plist)
{ "food" = { "price_random" = 0.15; }; "radioactives" = { "price_random" = 0.12; }; "liquor_wines" = { "price_random" = 0.15; }; "luxuries" = { "price_random" = 0.08; }; "luxuries" = { "price_random" = 0.08; }; "computers" = { "price_random" = 0.12; }; "machinery" = { "price_random" = 0.05; }; "alloys" = { "price_average" = 158; "price_economic" = 0.10; "price_random" = 0.06; }; "firearms" = { "price_random" = 0.20; }; "furs" = { "price_random" = 0.25; }; "alien_items" = { "price_economic" = 0.05; "price_random" = 0.45; }; }
A new commodity (from the Trade-goods.plist inside "SW Economy")
"medicine" = { "name" = "[commodity-name medicine]"; "classes" = ("oolite-consumer","oolite-medical"); "quantity_unit" = 1; "peak_export" = 7; "peak_import" = 0; "price_average" = 640; "price_economic" = 0.25; "price_random" = 0.15; "quantity_average" = 5; "quantity_economic" = 1.5; "quantity_random" = 1.0; "legality_export" = 0; "legality_import" = 0; "trumble_opinion" = 0.50; "sort_order" = 500; "comment" = "[oolite-commodity-medicine]"; };
Setting an orbital main station's slaves on sale to zero
Phkb provided this example which requires a planetinfo.plist & a new .js file - there are other ways to achieve the same result
1) planetinfo.plist (to invoke the market script)
(in the "Config" folder)
{ "0 96" = {market_script = "digebiti_noslaves.js";}; }
2) Market script: digebiti_noslaves.js
(in the "Scripts" folder) - note the complex name, which prevents confusion with other snippets of script
"use strict"; this.name = "digebiti_noslaves"; this.author = "Phkb"; this.copyright = "(C) 2022 Phkb"; this.licence = "CC-NC-by-SA 4.0"; this.description = "Removes slaves for sale in Digebiti"; this.version = "1.0"; this.updateLocalCommodityDefinition = function(goodDefinition, station, systemID) { if (goodDefinition.key == "slaves" && station && station.isMainStation) { goodDefinition.quantity = 0; } return goodDefinition; }
This method will work, so long as no other OXP changes the market_script property of the Digebiti system in the same way.
Creating an invariant market independent of the local system economy (agricultural/industrial)
If you just use a market_definition in shipdata.plist for the station, then you're merely setting variations on the primary system market.
If you want the station prices to be independent of (or more loosely tied to) the system it's in, then you need to use a market script.
So what you'd have is something like this - let's say it's a hydroponics station, so it always sells food and nothing else, regardless of whether the general system economy is industrial or agricultural:
// this is going to get called once for each trade good on the market function updateLocalCommodityDefinition (goodDefinition, station, system) { // at this point, goodDefinition will contain the trade-goods.plist information on the commodity itself // and a price and quantity key to say what the processing so far has done in mutating the default system market // so that might say "price" = 35; "quantity" = 50; // the important thing is that we can just ignore that entirely if we want if (goodDefinition.key == "food") { // are we calculating the basic Oolite "food" trade good right now? If yes, set our own static prices goodDefinition.price = 25; // 2.5 credits goodDefinition.quantity = 80; // lots of food } else { // if not, don't buy or sell any other commodity // TODO: work out what we should import goodDefinition.price = 0; goodDefinition.quantity = 0; goodDefinition.capacity = 0; } return goodDefinition; }
Now ... the reason that market definitions are preferred to be mutators of the system economy is for two reasons. 1) it means you can avoid the situation where you end up with two adjacent stations with wildly different prices ... but you can of course handle that in other ways too, station positioning, restricted access, etc. 2) it makes it easier to handle OXP trade goods:
- other OXPs might have added more "food" type goods, e.g. "Meat" or "Cheese" - you can learn about those and handle them specifically
} else if (goodDefinition.key == "bobsfoodoxp_cheese") { // set price and quantity here
... but that means a lot of keeping up with what everyone else is doing with custom goods.
The idea was that for your hydroponics station what you might do instead is something like this
// this is going to get called once for each trade good on the market function updateLocalCommodityDefinition (goodDefinition, station, system) { // at this point, goodDefinition will contain the trade-goods.plist information on the commodity itself // and a price and quantity key to say what the processing so far has done in mutating the default system market // so that might say "price" = 35; "quantity" = 50; // the important thing is that we can just ignore that entirely if we want if (goodDefinition.classes.indexOf("oolite-edible") >= 0) { // has this commodity been declared either by Oolite Core or by the OXP that added it as // some sort of food? goodDefinition.price = goodDefinition.price * 0.6; // big discount for buying at source goodDefinition.quantity = 10 + (goodDefinition.quantity * 2); // lots of it, and at least 10t even in heavily industrial systems } else { // if not, don't buy or sell any other commodity // TODO: work out what we should import goodDefinition.price = 0; goodDefinition.quantity = 0; goodDefinition.capacity = 0; } return goodDefinition; }
So now if another OXP adds Cheese, provided they declare it as "edible", your station will sell it in high quantities at a big discount, but you don't need to know exactly what the baseline price or quantity was set - so if a food-focused OXP was to add "Wine", "Fine Wine" and "Vintage Wine" at a range of prices and quantities, you'd be automatically handling those in reasonable line with a combination of the Wine OXP's basic pricing structure and your station OXP's "food primary source market" style, without necessarily needing to know about every future Wine OXP there might be and explicitly set compatibility.
Note in this example we're still zeroing out all non-food goods - OXP included! - regardless of the local economy. Maybe at this point we'd want to include and document a "hydroponicsoxp-agriculturalequipment" trade good class that other OXPs could use, which this station would buy up at higher demand and prices than normal.
// this is going to get called once for each trade good on the market function updateLocalCommodityDefinition (goodDefinition, station, system) { // at this point, goodDefinition will contain the trade-goods.plist information on the commodity itself // and a price and quantity key to say what the processing so far has done in mutating the default system market // so that might say "price" = 35; "quantity" = 50; // the important thing is that we can just ignore that entirely if we want if (goodDefinition.classes.indexOf("oolite-edible") >= 0) { // has this commodity been declared either by Oolite Core or by the OXP that added it as // some sort of food? goodDefinition.price = goodDefinition.price * 0.6; // big discount for buying at source goodDefinition.quantity = 10 + (goodDefinition.quantity * 2); // lots of it, and at least 10t even in heavily industrial systems } else if (goodDefinition.classes.indexOf("hydroponicsoxp-agriculturalequipment") >= 0) { // custom class for other OXPs to add specific agricultural equipment e.g. combine harvesters, compost, fertiliser goodDefinition.price = goodDefinition.price * 1.3; // better prices here goodDefinition.capacity = 16; // buy a small amount only // TODO: raise capacity if price per tonne is low goodDefinition.quantity = 0; // not selling this } else if (goodDefinition.key == "machinery") { // the basic Oolite Machinery commodity isn't in the custom class but we need to buy something if // no OXPs adding it are installed goodDefinition.price = goodDefinition.price * 1.3; // better prices here goodDefinition.quantity = 0; // not selling goodDefinition.capacity = 16; // small capacity to buy } else { // don't buy or sell any other commodity goodDefinition.price = 0; goodDefinition.quantity = 0; goodDefinition.capacity = 0; } return goodDefinition; }
So you can see this one has a mix of using, mutating and overruling the definitions set by the system market and OXP commodity definitions ... with this method you could have a whole bunch of OXPs setting up themed stations, and a whole different bunch adding their own custom commodities, and they'd barely have to know about each other to ensure reasonable consistency and compatibility from someone installing any combination of them. (- by Cim, 2023)
Note: this gives examples for 1 commodity at a time, but not how to easily do that for ALL the commodities on ALL the extra stations in a system.
Using the new Trade-goods.plist
The original commodities.plist was really not very good - it was based very strongly on the original Elite 84 pricing algorithm, which was written to use peculiarities of 8-bit Maths to its advantage. So one ended up with a series of fairly incomprehensible integers defining each market.
The trade-goods.plist was designed to make that simpler, by:
- 1) Making it a dictionary rather than an unlabelled array so one could tell which parameter was which
- 2) Making the price range for a trade good dependent on more sensible variables, so rather than setting the price range with a bitmask, one set the price and quantity variations dependent on the economy and on random chance.
Compare the v.1.81 trade-goods.plist with a v.1.80 commodities.plist - they were designed to give near-identical market behaviour for the built-in trade goods.
But - that only made it more comprehensible. It didn't really let one do anything particularly different to Elite 84 markets - mostly including the "Rich Industrial -> Poor Agricultural" single spectrum. One can tweak that a bit with peak_export and peak_import to get slightly different shapes (so a good might peak at Poor Industrial rather than Rich Industrial) but it doesn't really give one the option of a third economy type still.
So how does one introduce a new economic pole, such as a mining industry?
For that, one needs the scripting support. Oolite JavaScript Reference: Market Scripts.
One can quite literally do whatever one likes with the prices that way. One could have every *system* have its own separate economy, which varied over time according to events, what the player had previously traded, etc. etc. to produce something comparable to Elite Dangerous' economic sim, if that's the route one wanted to go down. Obviously the more complexity one adds the harder it gets to script up, but it's now all possible.
SOTL Altmap does have an example of it, which basically throws out the original Elite-like algorithm of trade-goods.plist and starts over. It goes *way* beyond just adding a third economy, too.
Basically in SOTL every trade good is tagged in the trade-goods.plist with a bunch of classes e.g.
classes = ("sotl-ex-salvage","sotl-im-refining","sotl-quantity-high","sotl-priceband-3","sotl-demand-medium","sotl-supply-medium","sotl-volatility-low");
So that means:
- exported by salvage economies - imported by refining economies - high quantity, moderate price, low price volatility
Then the market script uses those classes, rather than the standard pricing variables, to define the economic behaviour.
- So line 69-74ish: get the current economy description, convert that to the format used in the class name
- ...line 85-90: check for the import and export classes for that economy and see if it's imported or exported
- ...lines 100-120: apply various special conditions, so e.g. the sotl-ims-radiation class checks the planet surface radiation levels (custom property in sysinfo) and if the radiation level is high, imports these goods even if the basic economy isn't interested (e.g. "sotl-ceramics-ind" in the plist)
- ...and then it uses those calculations (and a bunch more of the classes) in the rest of the script to set the price and quantity depending on whether it's an import, an export, or neither
That's massive overkill for just adding a third mining economy, but one would need to work on a similar principle.
- 1) Tag every commodity with classes for (import|export|none)-(agricultural|industrial|mining) as appropriate
- 2) Adjust the economy names (there are a few different ways one could do that) so that the 8 economy IDs were allocated to agricultural, industrial and mining economies
- 3) Have a price script which like the SOTL one gets the current economy, looks at the classes list to find out if the commodity is imported or exported or neither, and sets the price and quantity accordingly. One could use the standard price_economic, price_average, price_random properties in the plist to give baseline data to the script if one was not planning to do anything too weird.
{Technically one doesn't need the classes - just have the price script contain a list of goods internally for each economy - but this way if other people add extra trade goods in their own OXPs, they can tag them up for mining economy support too}
All fairly straightforward theoretically.
The biggest impediment to doing a wholesale reworking of the economic system, is just how incompatible it makes lots and lots of OXP's. In order to override the vanilla commodities as in SOTL, requires setting a script for each in trade-goods.plist. Which is fine if it's the only OXP that wants to play around with commodity prices. As soon as two OXP's try to do the same thing, one gets a conflict and no longer knows which OXP pricing model is in play.
For SOTL it's fine, because it uses the scenario system to forcibly eject all other OXP's from use while it's running. But if one wants the OXP to go in the general playing pool set of OXP's, it becomes much more complicated. Phkb spent a long time trying to work out why his market script wasn't running, only to find that UPS Courier was quietly adjusting the markets in certain systems via the planetinfo.plist file. And there are a lot of OXP's that want to play in this space, tweaking prices up or down.
Hence one usually abandons using any of the script options, and just tweaks the price after all the scripts have finished playing, eg in Smugglers. It was the only way to guarantee getting some sort of bump in a price when wanted.
Links
- Oolite JavaScript Reference: Market Scripts
- Commodities.plist - the precursor (which still mostly works!). If it doesn't see here (2015)
- Proposal for 1.82: support for economic changes (Cim, 2014)
- Cim on introducing the Trade-goods.plist and deprecating the commodities.plist (2014)
Trade goods work is coming along fairly well. As well as plist-coded prices for trade goods, which should handle the simple cases (including a few things not possible with commodities.plist), you can now specify a "market script" which allows you to set prices per trade-good, per station, or per system. If you try to do all three, the following happens: 1) The per-trade good script sets a standard price and quantity for the system's economy and any other data it considers |
(Cim in Progress Thread (2014)) |
Using the Trade-goods.plist
- How to get the displayName for a commodity in the Main Station Market? (2020)
- Thinking about Planet Income classification (2015)
In 1.81 this is a lot easier to OXP - you can introduce as many economies as you like, though you are limited to eight economy symbols (so if you wanted lots of economy types, you might end up with Poor Industrial, Avg Industrial and Rich Industrial sharing a symbol on the map). The trade good pricing scripts can then be used to set up different price categories in different economies (and introduce additional trade goods, or split an existing good into multiple categories, which may be necessary if you're adding more economies).
Resetting economies across the planets is a big change in terms of lines of code, but is also very easy to automate. If OXPers are looking for a scripting tool to use for this sort of automation, then NodeJS or io.js is free and also uses Javascript as its language, so you don't need to learn a new one - and may even be able to share code between your build scripts and your OXP, though I haven't tried that yet. |
(Cim in Planet Income thread (2015)) |