Here is the (relevant part of the) code for calculating revenue in Simutrans-Standard:
if( welt->get_einstellungen()->get_pay_for_total_distance_mode()==einstellungen_t::TO_DESTINATION ) {
// pay only the distance, we get closer to our destination
while( iter.next() ) {
const ware_t & ware = iter.get_current();
if( ware.menge==0 ) {
continue;
}
// now only use the real gain in difference for the revenue (may as well be negative!)
const koord &zwpos = ware.get_zielpos();
const long dist = koord_distance( zwpos, start ) - koord_distance( end, zwpos );
const sint32 grundwert128 = ware.get_besch()->get_preis()<<7; // bonus price will be always at least 0.128 of the real price
const sint32 grundwert_bonus = (ware.get_besch()->get_preis()*(1000+speed_base*ware.get_besch()->get_speed_bonus()));
const sint64 price = (sint64)(grundwert128>grundwert_bonus ? grundwert128 : grundwert_bonus) * (sint64)dist * (sint64)ware.menge;
// sum up new price
value += price;
}
}
else if( welt->get_einstellungen()->get_pay_for_total_distance_mode()==einstellungen_t::TO_TRANSFER ) {
// pay distance traveled to next trasnfer stop
while( iter.next() ) {
const ware_t & ware = iter.get_current();
if(ware.menge==0 || !ware.get_zwischenziel().is_bound()) {
continue;
}
// now only use the real gain in difference for the revenue (may as well be negative!)
const koord zwpos = ware.get_zwischenziel()->get_basis_pos();
const long dist = koord_distance( zwpos, start ) - koord_distance( end, zwpos );
const sint32 grundwert128 = ware.get_besch()->get_preis()<<7; // bonus price will be always at least 0.128 of the real price
const sint32 grundwert_bonus = (ware.get_besch()->get_preis()*(1000+speed_base*ware.get_besch()->get_speed_bonus()));
const sint64 price = (sint64)(grundwert128>grundwert_bonus ? grundwert128 : grundwert_bonus) * (sint64)dist * (sint64)ware.menge;
// sum up new price
value += price;
}
}
else {
// pay distance traveled
const long dist = koord_distance( start, end );
while( iter.next() ) {
const ware_t & ware = iter.get_current();
if(ware.menge==0 || !ware.get_zwischenziel().is_bound()) {
continue;
}
// now only use the real gain in difference for the revenue (may as well be negative!)
const sint32 grundwert128 = ware.get_besch()->get_preis()<<7; // bonus price will be always at least 0.128 of the real price
const sint32 grundwert_bonus = (ware.get_besch()->get_preis()*(1000+speed_base*ware.get_besch()->get_speed_bonus()));
const sint64 price = (sint64)(grundwert128>grundwert_bonus ? grundwert128 : grundwert_bonus) * (sint64)dist * (sint64)ware.menge;
// sum up new price
value += price;
}
}
In Simutrans-Experimental, it is different:
// 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_journey_time_multiplier() * 60);
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(ware.is_p****enger())
{
//P****engers care about their comfort
const uint8 tolerable_comfort = calc_tolerable_comfort(journey_minutes);
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();
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;
const float multiplier = welt->get_einstellungen()->get_max_discomfort_penalty();
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_p****enger())
{
// P****engers
float proportion = 0.0F;
switch(catering_level)
{
case 1:
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;
case 2:
case_2:
if(journey_minutes < welt->get_einstellungen()->get_catering_level1_minutes())
{
// If only C++ had C#'s goto case syntax...
goto case_1;
}
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 3:
case_3:
if(journey_minutes < welt->get_einstellungen()->get_catering_level2_minutes())
{
goto case_2;
}
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 4:
case_4:
if(journey_minutes < welt->get_einstellungen()->get_catering_level3_minutes())
{
goto case_3;
}
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 5:
default:
if(journey_minutes < welt->get_einstellungen()->get_catering_level4_minutes())
{
goto case_4;
}
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;
};
}
}