The m4nfo User manual and Technical Report

Handling 'real' prices and costs

Introduction

TTD, the precursor of TTDPatch and OpenTTD, introduced a quite simplistic and inflexible system for handling vehicle prices and costs. It is build on so-called base costs where vehicle prices are derived from fixed base costs multiplied by byte-sized factors.

E.g., the base cost for train engines is £ 400,000, and the price for a vehicle with a factor n is set to 400,000 / 256 * n.

TTDPatch and OpenTTD improved that system by adding 'cost base multipliers' which allow to change base costs by factors of two. This allowed for much larger ranges for price and cost values, but, on the other hand, it also enlarges price/cost steps between factors, and made price finding even more convoluted, still limiting the scope for designing best fitting price and cost schemes.

With inflation and currency conversion added, this system makes it still hard for set developers to achieve 'real' (historic) prices and costs for vehicle sets. Thus, m4nfo not only supports the traditional approach with explicitly using price/cost factors and base cost multipliers, but offers a new strategy of 'real' price and cost handling.

Real price and cost handling

To do so, in a first step, m4nfo calculates the needed base cost factor (see tables) to properly represent the price for the most expensive vehicle of a set (by functions maxengineprice() and maxwagonprice()), and then calculates correct price factors for every other vehicle using this base cost factor by property function price(), see example below.

Needless to say that prices entered that way still rely on game difficulty settings and inflation.

Function Description
setcurrency(<Currency>)Currency to be used for price/cost calculations
maxengineprice(<Dword>)Highest price for an engine in vehicle set
maxwagonprice(<Dword>)Highest price for a coach or freight wagon in vehicle set
maxenginecost(<Dword>)Highest running cost for an engine in vehicle set
maxwagoncost(<Dword>)Highest running cost for a coach or freight wagon in vehicle set

Description

setcurrency(<Currency>)

This function sets the currency to be used for price/cost calculations. Supported currencies are as follows:

Currency Description Factor vs GBP
ATSAustrian Schilling27
BEFBelgian Franc81
CHFSwiss Franc2
CZKCzech Koruna41
DEMGerman Mark4
DKKDanish Krone11
ESPSpanish Peseta333
EUREuro2
FIMFinnish Markka12
FRFFrench Franc13
GBPBritish pound1
GRDGreek Drachma681
HUFHungarian Forint378
ITLItalian Lira3873
JPYJapanese Yen220
NLGDutch Guilder4
NOKNorwegian Krona12
PLNPolish Złoty6
RURRussian Ruble50
SEKSwedish Krona13
SKKSlovak Koruna60
USDUS Dollar2

This function must be used before functions maxengineprice(), maxwagonprice(), maxenginecost(), and maxwagoncost().

maxengineprice(<Dword>), maxwagonprice(<Dword>)
maxenginecost(<Dword>), maxwagoncost(<Dword>)

These functions have to be used to set price and cost ranges for engine vehicles and wagons, accordingly. Parameters must be the highest prices/costs for vehicles in the set. Parameter values may use 'digit grouping', i.e. "10'000", "10.000" and "10 000" are allowed, but no commas as delimiters!

To provide a proper mapping of prices and costs onto TTD's base costs, parameters for these four functions are limited:

Function GBP USD DEM FRF ESP
maxengineprice 51'200'000 102'400'000 204'800'000 665'600'000 17'049'600'000
maxenginecost 614'400 1'228'800 2'457'600 7'987'200 204'595'200
maxwagonprice 256'000 512'000 1'024'000 3'328'000 85'248'000
maxwagoncost 204'800 409'600 819'200 2'662'400 68'198'400

These four functions return base cost multipliers, to be used inside function basecost(), see example below.

Example 1 (setting real prices):
setcurrency(DEM) // prices & costs in D-Mark

// purchase prices
skipif(2, getowngrfparameter(1),!=,0)
// "low" ::= parameter1 == 0
basecost(
	{BUILD_ENGINES, eval(maxengineprice(3'000'000) / 2)},
	{BUILD_WAGONS, eval(maxwagonprice(500'000) / 2)}
)

skipif(2, getowngrfparameter(1),!=,2)
// "high" ::= parameter1 == 2
basecost(
	{BUILD_ENGINES, eval(maxengineprice(3'000'000) * 2)},
	{BUILD_WAGONS, eval(maxwagonprice(500'000) * 2)}
)

// "normal" ::= parameter1 == 1
skipif(2, getowngrfparameter(1),!=,1)
basecost(
	{BUILD_ENGINES, maxengineprice(3'000'000)},
	{BUILD_WAGONS, maxwagonprice(500'000)}
)

As can be seen, functions maxengineprice()/maxenginecost() and maxwagonprice()/maxwagoncost() need to be used to set the correct base cost multiplicators. You might use function eval() to modify the base cost multiplicators returned by said functions. Please note that function enginetype() implicitly defines base costs to be used for said vehicle.

Example 2 (setting real running costs):
// running costs
skipif(2,getowngrfparameter(2),!=,0)
// "low" ::= parameter2 == 0
basecost(
	{RUN_ENGINES, eval(maxenginecost(200'000) / 2)},
	{RUN_WAGONS, eval(maxwagoncost(10'000) / 2)}
)

skipif(2,getowngrfparameter(2),!=,2)
// "high" ::= parameter2 == 2
basecost(
	{RUN_ENGINES, eval(maxenginecost(200'000) * 2)},
	{RUN_WAGONS, eval(maxwagoncost(10'000) * 2)}
)

// "normal" ::= parameter2 == 1 // *16 ; *2
skipif(2, getowngrfparameter(2),!=,1)
basecost(
	{RUN_ENGINES, maxenginecost(200'000)},
	{RUN_WAGONS, maxwagoncost(10'000)},
)

...

definevehicle(_V100, {"V 100 / 212 (DB)"},
	newgraphics()
	lifecycle(1-1-1962, 1990, 2010)
	enginetype(DIESEL) // traction type & cost base
	...
	price(500'000)
	runningcost(25'000)
)

...

definevehicle(_PDISTLONG, {"Passenger coach (main line)"},
	newgraphics()
	lifecycle(1-1-1950, 2040, 2050)
	enginetype(COACH)
	...
	price(56'000)
	runningcost(2'800)
)

setengineprice(<Dword>), setwagonprice(<Dword>), setenginecost(<Dword>), setwagoncost(<Dword>)

It is possible to set real prices dynamically by CB_PROP and helper functions setengineprice(), setwagonprice(), setenginecost(), and setwagoncost(). In case of distributed source files, it is important to "import" price and cost factors previously calculated by functions maxengineprice()/maxwagonprice() and maxenginecost()/maxwagoncost().

Example 3 (setting real price/cost dynamically):
import(m4_cost) // use price/cost scheme defined in other file

// V100

// lower prices for older models
def(1) year(
	setengineprice(500'000) if(< 1974) // full price as of 1962
	setengineprice(350'000) if(1974 .. 1988)
	setengineprice(245'000) else
)

// higher maintenance costs for older models
def(2) year(
	setenginecost(15'000) if(< 1974) // 3% of full price
	setenginecost(20'000) if(1974 .. 1988) // 4%
	setenginecost(25'000) else             // 5%
)

def(3) property(
	ref(1) if(price())
	ref(2) if(runningcost())
	cbfail() else
)

def(4) callback(
	ref(3) if(CB_PROP) // prices & costs
	ref(0) else	   // menu
)