News:

Simutrans.com Portal
Our Simutrans site. You can find everything about Simutrans from here.

Revenue calculation in experimental?

Started by TheUniqueTiger, February 04, 2011, 05:53:58 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

TheUniqueTiger

At present I'm playing two games, one with 102.2 standard and one with 9.2 exp, both with pak128 (not pakBritain). In standard, I can make profit by transporting crude oil, however transporting crude oil in experimental gives only a small revenue as compared to what is expected.
I always calculate the expected revenue by load*revenue/unit*number of tiles. However this calculation goes totally wrong here.
The specifics are as follows:
Transporting crude oil from a dock to refinery by rail. Train runs full with 992 cu.m of crude oil, with running cost of 9.51/km. My tiles/km setting is 10. So the revenue shown is 1.07/cu.m/km. I'm transporting them between stations with distance of 97 tiles between them, (50 N-S, 45 E-W) in L-shape route from source to destination. Going by my calculations, revenue should be (992*1.07 - 9.51) * 97/10 =  10,203. However I'm getting a revenue of 2,116 each trip which is close to (992*1.07 - 9.51) * 2. So my question is how is the revenue calculated? The revenue values in pakBritain-Ex are 5 times the pak128 values, which probably explains the normal profitability while playing it, but my question is whats the formula for revenue calculation? This implies pak128 with experimental is not profitable at all.

jamespetts

Here is the code for calculating the revenue for one packet of cargo/passengers whenever it is unloaded at the end of the journey. Note that one packet of cargo can contain more than one unit of cargo: this is the "menge" parameter (many of the variable names are in German).


sint64 convoi_t::calc_revenue(ware_t& ware)
{
float average_speed;

if(!line.is_bound())
{
// No line - must use convoy
if(financial_history[1][CONVOI_AVERAGE_SPEED] == 0)
{
average_speed = financial_history[0][CONVOI_AVERAGE_SPEED];
}
else
{
average_speed = financial_history[1][CONVOI_AVERAGE_SPEED];
}
}

else
{
if(line->get_finance_history(1, LINE_AVERAGE_SPEED) == 0)
{
average_speed = line->get_finance_history(0, LINE_AVERAGE_SPEED);
}
else
{
average_speed = line->get_finance_history(1, LINE_AVERAGE_SPEED);
}
}

// Cannot not charge for journey if the journey distance is more than a certain proportion of the straight line distance.
// This eliminates the possibility of cheating by building circuitous routes, or the need to prevent that by always using
// the straight line distance, which makes the game difficult and unrealistic.
// If the origin has been deleted since the packet departed, then the best that we can do is guess by
// trebling the distance to the last stop.
const uint32 max_distance = ware.get_origin().is_bound() ? accurate_distance(ware.get_origin()->get_basis_pos(), fahr[0]->get_pos().get_2d()) * 2.2 : 3 * accurate_distance(last_stop_pos.get_2d(), fahr[0]->get_pos().get_2d());
const uint32 distance = ware.get_accumulated_distance();
const uint32 revenue_distance = distance < max_distance ? distance : max_distance;

ware.reset_accumulated_distance();

//Multiply by a factor (default: 0.3) to ensure that it fits the scale properly. Journey times can easily appear too long.
uint16 journey_minutes = (((float)distance / average_speed) * welt->get_einstellungen()->get_distance_per_tile() * 60.0F);

const ware_besch_t* goods = ware.get_besch();
const uint16 price = goods->get_preis();
const sint32 min_price = price << 7;
const uint16 speed_bonus_rating = calc_adjusted_speed_bonus(goods->get_speed_bonus(), distance);
const sint32 ref_speed = welt->get_average_speed( fahr[0]->get_besch()->get_waytype() );
const sint32 speed_base = (100 * average_speed) / ref_speed - 100;
const sint32 base_bonus = (price * (1000 + speed_base * speed_bonus_rating));
const sint64 revenue = (sint64)(min_price > base_bonus ? min_price : base_bonus) * (sint64)revenue_distance * (sint64)ware.menge;
sint64 final_revenue = revenue;

const float happy_ratio = ware.get_origin().is_bound() ? ware.get_origin()->get_unhappy_proportion(1) : 1;
if(speed_bonus_rating > 0 && happy_ratio > 0)
{
// Reduce revenue if the origin stop is crowded, if speed is important for the cargo.
sint64 tmp = ((float)speed_bonus_rating / 100.0F) * revenue;
tmp *= (happy_ratio * 2);
final_revenue -= tmp;
}

if(final_revenue && ware.is_passenger())
{
//Passengers care about their comfort
const uint8 tolerable_comfort = calc_tolerable_comfort(journey_minutes);

// Comfort matters more the longer the journey.
// @author: jamespetts, March 2010
float comfort_modifier;
if(journey_minutes <= welt->get_einstellungen()->get_tolerable_comfort_short_minutes())
{
comfort_modifier = 0.2F;
}
else if(journey_minutes >= welt->get_einstellungen()->get_tolerable_comfort_median_long_minutes())
{
comfort_modifier = 1.0F;
}
else
{
const uint8 differential = journey_minutes - welt->get_einstellungen()->get_tolerable_comfort_short_minutes();
const uint8 max_differential = welt->get_einstellungen()->get_tolerable_comfort_median_long_minutes() - welt->get_einstellungen()->get_tolerable_comfort_short_minutes();
const float proportion = (float)differential / (float)max_differential;
comfort_modifier = (0.8F * proportion) + 0.2F;
}

uint8 comfort = 100;
if(line.is_bound())
{
if(line->get_finance_history(1, LINE_COMFORT) < 1)
{
comfort = line->get_finance_history(0, LINE_COMFORT);
}
else
{
comfort = line->get_finance_history(1, LINE_COMFORT);
}
}
else
{
// No line - must use convoy
if(financial_history[1][CONVOI_COMFORT] < 1)
{
comfort = financial_history[0][CONVOI_COMFORT];
}
else
{
comfort = financial_history[1][CONVOI_COMFORT];
}
}

if(comfort > tolerable_comfort)
{
// Apply luxury bonus
const uint8 max_differential = welt->get_einstellungen()->get_max_luxury_bonus_differential();
const uint8 differential = comfort - tolerable_comfort;
const float multiplier = welt->get_einstellungen()->get_max_luxury_bonus() * comfort_modifier;
if(differential >= max_differential)
{
final_revenue += (revenue * multiplier);
}
else
{
const float proportion = (float)differential / (float)max_differential;
final_revenue += revenue * (multiplier * proportion);
}
}
else if(comfort < tolerable_comfort)
{
// Apply discomfort penalty
const uint8 max_differential = welt->get_einstellungen()->get_max_discomfort_penalty_differential();
const uint8 differential = tolerable_comfort - comfort;
float multiplier = welt->get_einstellungen()->get_max_discomfort_penalty() * comfort_modifier;
multiplier = multiplier < 0.95F ? multiplier : 0.95F;
if(differential >= max_differential)
{
final_revenue -= (revenue * multiplier);
}
else
{
const float proportion = (float)differential / (float)max_differential;
final_revenue -= revenue * (multiplier * proportion);
}
}

// Do nothing if comfort == tolerable_comfort
}

//Add catering or TPO revenue
const uint8 catering_level = get_catering_level(ware.get_besch()->get_catg_index());
if(catering_level > 0)
{
if(ware.is_mail())
{
// Mail
if(journey_minutes >= welt->get_einstellungen()->get_tpo_min_minutes())
{
final_revenue += (welt->get_einstellungen()->get_tpo_revenue() * ware.menge);
}
}
else if(ware.is_passenger())
{
// Passengers
float proportion = 0.0F;
// Knightly : Reorganised the switch cases to get rid of goto statements
switch(catering_level)
{

case 5:
default:
if(journey_minutes >= welt->get_einstellungen()->get_catering_level4_minutes())
{
if(journey_minutes > welt->get_einstellungen()->get_catering_level5_minutes())
{
final_revenue += (welt->get_einstellungen()->get_catering_level5_max_revenue() * ware.menge);
break;
}

proportion = (journey_minutes - welt->get_einstellungen()->get_catering_level4_max_revenue()) / (welt->get_einstellungen()->get_catering_level5_minutes() - welt->get_einstellungen()->get_catering_level4_minutes());
final_revenue += (proportion * (welt->get_einstellungen()->get_catering_level5_max_revenue() * ware.menge));
break;
}

case 4:
if(journey_minutes >= welt->get_einstellungen()->get_catering_level3_minutes())
{
if(journey_minutes > welt->get_einstellungen()->get_catering_level4_minutes())
{
final_revenue += (welt->get_einstellungen()->get_catering_level4_max_revenue() * ware.menge);
break;
}

proportion = (journey_minutes - welt->get_einstellungen()->get_catering_level3_max_revenue()) / (welt->get_einstellungen()->get_catering_level4_minutes() - welt->get_einstellungen()->get_catering_level3_minutes());
final_revenue += (proportion * (welt->get_einstellungen()->get_catering_level4_max_revenue() * ware.menge));
break;
}

case 3:
if(journey_minutes >= welt->get_einstellungen()->get_catering_level2_minutes())
{
if(journey_minutes > welt->get_einstellungen()->get_catering_level3_minutes())
{
final_revenue += (welt->get_einstellungen()->get_catering_level3_max_revenue() * ware.menge);
break;
}

proportion = (journey_minutes - welt->get_einstellungen()->get_catering_level2_max_revenue()) / (welt->get_einstellungen()->get_catering_level3_minutes() - welt->get_einstellungen()->get_catering_level2_minutes());
final_revenue += (proportion * (welt->get_einstellungen()->get_catering_level3_max_revenue() * ware.menge));
break;
}

case 2:
if(journey_minutes >= welt->get_einstellungen()->get_catering_level1_minutes())
{
if(journey_minutes > welt->get_einstellungen()->get_catering_level2_minutes())
{
final_revenue += (welt->get_einstellungen()->get_catering_level2_max_revenue() * ware.menge);
break;
}

proportion = (journey_minutes - welt->get_einstellungen()->get_catering_level1_max_revenue()) / (welt->get_einstellungen()->get_catering_level2_minutes() - welt->get_einstellungen()->get_catering_level1_minutes());
final_revenue += (proportion * (welt->get_einstellungen()->get_catering_level2_max_revenue() * ware.menge));
break;
}

case 1:
if(journey_minutes < welt->get_einstellungen()->get_catering_min_minutes())
{
break;
}
if(journey_minutes > welt->get_einstellungen()->get_catering_level1_minutes())
{
final_revenue += (welt->get_einstellungen()->get_catering_level1_max_revenue() * ware.menge);
break;
}

proportion = (journey_minutes - welt->get_einstellungen()->get_catering_min_minutes()) / (welt->get_einstellungen()->get_catering_level1_minutes() - welt->get_einstellungen()->get_catering_min_minutes());
final_revenue += (proportion * (welt->get_einstellungen()->get_catering_level1_max_revenue() * ware.menge));
break;

};
}
}

final_revenue = (final_revenue + 1500ll) / 3000ll;

return final_revenue;
}


For goods (i.e., not passengers or mail), you can ignore everything from if(final_revenue && ware.is_passenger()) downwards.
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

TheUniqueTiger

Thanks for the code...
But I really cannot relate to my example. Going by the formula my revenue comes out something different rather than the actual amount in the game. I know for sure that my line average speed should be lower than the benchmark 80km/h, however as oil has no speedbonus advantage, this really doesn't count, isn't it?
By all calculations that I could think of, the actual value comes out to be (predicted value / 5). Is there any explanation for this? My main purpose for opening this thread is knowing why Simutrans standard gives (x) amount while Simutrans experimental gives (x/5) amount.

jamespetts

QuoteTransporting crude oil from a dock to refinery by rail. Train runs full with 992 cu.m of crude oil, with running cost of 9.51/km. My tiles/km setting is 10. So the revenue shown is 1.07/cu.m/km.

May I ask - where did you get the figure of 1.07/m^3/km? If from the list of goods, you may have misinterpreted it: in Experimental, if you enter a distance (in your case, 10km), the price value shown is the price for the whole distance, not per kilometre. The per kilometre price in Pak128 is 0.11c.

Your problem in making profit may well be due to your very, very high tiles/km setting of 10: you are transporting the crude oil only 10km, which would not get you much revenue in any distance per tile setting, although it looks like a deceptively long route  because of your scale setting. A distance per tile setting of 0.25km/tile works well in Pak128.Britain-Ex; major deviations from that may produce suboptimal results.
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

TheUniqueTiger

Yes James, the value is 0.11 for 1 tile/km. Increasing tile/km setting increases the value roughly in multiples of 0.11. For 10 tiles/km, its shown as 1.07 (and not 1.10 as expected). I'll try with 1 tile/km in another map to see if it works with pak128.

For Simutrans standard, the profit/tile and if tile/km is implemented, then profit/km used to be constant. Total revenue was proportional to distance (either direct or circuitous total). Here do you mean to say profit/km is variable and directly proportional to distance?

jamespetts

Where are you finding the values - the goods list? To what did you set the journey distance on the goods list to get 1.07?

I'm not sure what you mean by:

Quote
For Simutrans standard, the profit/tile and if tile/km is implemented, then profit/km used to be constant,

since Standard has no km/tile setting.
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

TheUniqueTiger

#6
Its very simple... Open the goods window (shift+g). At the top is tiles/km setting (distance). If its 1, then crude oil price is shown as 0.11. If its made 4, crude oil price becomes 0.43. If its made 10, crude oil price becomes 1.07.

What I meant by profit/tile or profit/km is constant is as follows:
Suppose a goods price for unit/km is 0.20 and I am transporting 500 units for a distance of 100 km or tiles (as Simutrans standard is 1tile/km). The running cost is 5.00/km. Exclude speedbonus for this one. So the revenue will be ((0.20 * 500 - 5) * 100) = 9,500. If I transported it for 200 km or tiles, it would be 19,000. The profit/km in this case is constant at (0.2 * 500 - 5) = 95. In case tiles/km is implemented still it should be constant in my opinion as (constant/constant) is another constant.

The flashing amount on delivery would be 10,000 but I know I've spent 500 for it, so while thinking about the line, I'll think the revenue earned is 9,500 per delivery.

jamespetts

At least one of us is confused, I think. When you refer to the "tiles/km setting", do you mean the "distance_per_tile" parameter in simuconf.tab or the distance setting in the goods list window?
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

TheUniqueTiger

I'm talking about the distance setting in the goods list window... Isn't it the same as tiles/km setting?

jamespetts

Ahh, no! It's totally different. This is where the confusion originates, I think. The distance setting in the goods list window shows the revenue of particular types of goods transported over certain distances. They are not per kilometre values, but total distance values. For example, if you set the distance to 10, it will show you the revenue earned from transporting the goods 10km. This is necessary in Experimental, because, unlike Standard, the speed bonus and comfort rating have a differential effect on revenues depending on the distances travelled: you will see this by adjusting the speed bonus and comfort ratings at different distances. At 1km it makes no difference - at a long distance, it can make a large difference. Changing this value has no effect on the game at all, and only changes what is displayed in the list of goods window - in the same way as the other settings in that window, and all the list windows.

The kilometres per tile setting is a parameter in simuconf.tab (multiplied by 10, so that 25 = 0.25km/tile) that sets the game scale. To access this from the GUI, you need to use the "settings" menu and look for the "experimental" tab.

I hope that this clarifies matters now!
Download Simutrans-Extended.

Want to help with development? See here for things to do for coding, and here for information on how to make graphics/objects.

Follow Simutrans-Extended on Facebook.

TheUniqueTiger

Oh, I see now. So in my case I came across distance per tile to be 0.25km/tile. If I'm transporting oil over 95 tiles it means 24 kms. So for 24 kms, the value shown for crude oil is 2.56. 992 units together make 2539. The revenue shown is less than this but its close, so I accept it.

Thanks.