OpenTTD/src/economy.cpp

1692 lines
55 KiB
C++
Raw Normal View History

/* $Id$ */
/** @file economy.cpp Handling of the economy. */
#include "stdafx.h"
#include "openttd.h"
#include "tile_cmd.h"
#include "company_func.h"
#include "command_func.h"
#include "industry_map.h"
#include "town.h"
#include "news_func.h"
#include "network/network.h"
#include "network/network_func.h"
#include "vehicle_gui.h"
#include "ai/ai.hpp"
#include "aircraft.h"
#include "train.h"
#include "newgrf_engine.h"
#include "newgrf_sound.h"
#include "newgrf_industries.h"
#include "newgrf_industrytiles.h"
#include "newgrf_station.h"
#include "unmovable.h"
#include "group.h"
#include "strings_func.h"
#include "functions.h"
#include "window_func.h"
#include "date_func.h"
#include "vehicle_func.h"
#include "sound_func.h"
#include "gfx_func.h"
#include "autoreplace_func.h"
#include "company_gui.h"
#include "signs_base.h"
#include "subsidy_func.h"
#include "station_base.h"
#include "table/strings.h"
#include "table/sprites.h"
/**
* Multiply two integer values and shift the results to right.
*
* This function multiplies two integer values. The result is
* shifted by the amount of shift to right.
*
* @param a The first integer
* @param b The second integer
* @param shift The amount to shift the value to right.
* @return The shifted result
*/
static inline int32 BigMulS(const int32 a, const int32 b, const uint8 shift)
{
return (int32)((int64)a * (int64)b >> shift);
}
/**
* Multiply two unsigned integers and shift the results to right.
*
* This function multiplies two unsigned integers. The result is
* shifted by the amount of shift to right.
*
* @param a The first unsigned integer
* @param b The second unsigned integer
* @param shift The amount to shift the value to right.
* @return The shifted result
*/
static inline uint32 BigMulSU(const uint32 a, const uint32 b, const uint8 shift)
{
return (uint32)((uint64)a * (uint64)b >> shift);
}
typedef SmallVector<Industry *, 16> SmallIndustryList;
/* Score info */
const ScoreInfo _score_info[] = {
{ SCORE_VEHICLES, 120, 100 },
{ SCORE_STATIONS, 80, 100 },
{ SCORE_MIN_PROFIT, 10000, 100 },
{ SCORE_MIN_INCOME, 50000, 50 },
{ SCORE_MAX_INCOME, 100000, 100 },
{ SCORE_DELIVERED, 40000, 400 },
{ SCORE_CARGO, 8, 50 },
{ SCORE_MONEY, 10000000, 50 },
{ SCORE_LOAN, 250000, 50 },
{ SCORE_TOTAL, 0, 0 }
};
int _score_part[MAX_COMPANIES][SCORE_END];
Economy _economy;
Prices _price;
uint16 _price_frac[NUM_PRICES];
Money _cargo_payment_rates[NUM_CARGO];
uint16 _cargo_payment_rates_frac[NUM_CARGO];
Money _additional_cash_required;
Money CalculateCompanyValue(const Company *c)
{
Owner owner = c->index;
Money value = 0;
Station *st;
uint num = 0;
FOR_ALL_STATIONS(st) {
if (st->owner == owner) num += CountBits(st->facilities);
}
value += num * _price.station_value * 25;
Vehicle *v;
FOR_ALL_VEHICLES(v) {
if (v->owner != owner) continue;
if (v->type == VEH_TRAIN ||
v->type == VEH_ROAD ||
(v->type == VEH_AIRCRAFT && IsNormalAircraft(v)) ||
v->type == VEH_SHIP) {
value += v->value * 3 >> 1;
}
}
/* Add real money value */
value -= c->current_loan;
value += c->money;
return max(value, (Money)1);
}
/** if update is set to true, the economy is updated with this score
* (also the house is updated, should only be true in the on-tick event)
* @param update the economy with calculated score
* @param c company been evaluated
* @return actual score of this company
* */
int UpdateCompanyRatingAndValue(Company *c, bool update)
{
Owner owner = c->index;
int score = 0;
memset(_score_part[owner], 0, sizeof(_score_part[owner]));
/* Count vehicles */
{
Vehicle *v;
Money min_profit = 0;
bool min_profit_first = true;
uint num = 0;
FOR_ALL_VEHICLES(v) {
if (v->owner != owner) continue;
if (IsCompanyBuildableVehicleType(v->type) && v->IsPrimaryVehicle()) {
num++;
if (v->age > 730) {
/* Find the vehicle with the lowest amount of profit */
if (min_profit_first || min_profit > v->profit_last_year) {
min_profit = v->profit_last_year;
min_profit_first = false;
}
}
}
}
min_profit >>= 8; // remove the fract part
_score_part[owner][SCORE_VEHICLES] = num;
/* Don't allow negative min_profit to show */
if (min_profit > 0)
_score_part[owner][SCORE_MIN_PROFIT] = ClampToI32(min_profit);
}
/* Count stations */
{
uint num = 0;
const Station *st;
FOR_ALL_STATIONS(st) {
if (st->owner == owner) num += CountBits(st->facilities);
}
_score_part[owner][SCORE_STATIONS] = num;
}
/* Generate statistics depending on recent income statistics */
{
int numec = min(c->num_valid_stat_ent, 12);
if (numec != 0) {
const CompanyEconomyEntry *cee = c->old_economy;
Money min_income = cee->income + cee->expenses;
Money max_income = cee->income + cee->expenses;
do {
min_income = min(min_income, cee->income + cee->expenses);
max_income = max(max_income, cee->income + cee->expenses);
} while (++cee, --numec);
if (min_income > 0) {
_score_part[owner][SCORE_MIN_INCOME] = ClampToI32(min_income);
}
_score_part[owner][SCORE_MAX_INCOME] = ClampToI32(max_income);
}
}
/* Generate score depending on amount of transported cargo */
{
const CompanyEconomyEntry *cee;
int numec;
uint32 total_delivered;
numec = min(c->num_valid_stat_ent, 4);
if (numec != 0) {
cee = c->old_economy;
total_delivered = 0;
do {
total_delivered += cee->delivered_cargo;
} while (++cee, --numec);
_score_part[owner][SCORE_DELIVERED] = total_delivered;
}
}
/* Generate score for variety of cargo */
{
uint num = CountBits(c->cargo_types);
_score_part[owner][SCORE_CARGO] = num;
if (update) c->cargo_types = 0;
}
/* Generate score for company's money */
{
if (c->money > 0) {
_score_part[owner][SCORE_MONEY] = ClampToI32(c->money);
}
}
/* Generate score for loan */
{
_score_part[owner][SCORE_LOAN] = ClampToI32(_score_info[SCORE_LOAN].needed - c->current_loan);
}
/* Now we calculate the score for each item.. */
{
int total_score = 0;
int s;
score = 0;
for (ScoreID i = SCORE_BEGIN; i < SCORE_END; i++) {
/* Skip the total */
if (i == SCORE_TOTAL) continue;
/* Check the score */
s = Clamp(_score_part[owner][i], 0, _score_info[i].needed) * _score_info[i].score / _score_info[i].needed;
score += s;
total_score += _score_info[i].score;
}
_score_part[owner][SCORE_TOTAL] = score;
/* We always want the score scaled to SCORE_MAX (1000) */
if (total_score != SCORE_MAX) score = score * SCORE_MAX / total_score;
}
if (update) {
c->old_economy[0].performance_history = score;
UpdateCompanyHQ(c, score);
c->old_economy[0].company_value = CalculateCompanyValue(c);
}
InvalidateWindow(WC_PERFORMANCE_DETAIL, 0);
return score;
}
/* use INVALID_OWNER as new_owner to delete the company. */
void ChangeOwnershipOfCompanyItems(Owner old_owner, Owner new_owner)
{
Town *t;
CompanyID old = _current_company;
assert(old_owner != new_owner);
{
Company *c;
uint i;
/* See if the old_owner had shares in other companies */
_current_company = old_owner;
FOR_ALL_COMPANIES(c) {
for (i = 0; i < 4; i++) {
if (c->share_owners[i] == old_owner) {
/* Sell his shares */
CommandCost res = DoCommand(0, c->index, 0, DC_EXEC, CMD_SELL_SHARE_IN_COMPANY);
/* Because we are in a DoCommand, we can't just execute an other one and
* expect the money to be removed. We need to do it ourself! */
SubtractMoneyFromCompany(res);
}
}
}
/* Sell all the shares that people have on this company */
c = Company::Get(old_owner);
for (i = 0; i < 4; i++) {
_current_company = c->share_owners[i];
if (_current_company != INVALID_OWNER) {
/* Sell the shares */
CommandCost res = DoCommand(0, old_owner, 0, DC_EXEC, CMD_SELL_SHARE_IN_COMPANY);
/* Because we are in a DoCommand, we can't just execute an other one and
* expect the money to be removed. We need to do it ourself! */
SubtractMoneyFromCompany(res);
}
}
}
_current_company = old_owner;
/* Temporarily increase the company's money, to be sure that
* removing his/her property doesn't fail because of lack of money.
* Not too drastically though, because it could overflow */
if (new_owner == INVALID_OWNER) {
Company::Get(old_owner)->money = UINT64_MAX >> 2; // jackpot ;p
}
if (new_owner == INVALID_OWNER) {
Subsidy *s;
for (s = _subsidies; s != endof(_subsidies); s++) {
if (s->cargo_type != CT_INVALID && s->age >= 12) {
if (Station::Get(s->to)->owner == old_owner) s->cargo_type = CT_INVALID;
}
}
}
/* Take care of rating in towns */
FOR_ALL_TOWNS(t) {
/* If a company takes over, give the ratings to that company. */
if (new_owner != INVALID_OWNER) {
if (HasBit(t->have_ratings, old_owner)) {
if (HasBit(t->have_ratings, new_owner)) {
/* use max of the two ratings. */
t->ratings[new_owner] = max(t->ratings[new_owner], t->ratings[old_owner]);
} else {
SetBit(t->have_ratings, new_owner);
t->ratings[new_owner] = t->ratings[old_owner];
}
}
}
/* Reset the ratings for the old owner */
t->ratings[old_owner] = RATING_INITIAL;
ClrBit(t->have_ratings, old_owner);
}
{
FreeUnitIDGenerator unitidgen[] = {
FreeUnitIDGenerator(VEH_TRAIN, new_owner), FreeUnitIDGenerator(VEH_ROAD, new_owner),
FreeUnitIDGenerator(VEH_SHIP, new_owner), FreeUnitIDGenerator(VEH_AIRCRAFT, new_owner)
};
Vehicle *v;
FOR_ALL_VEHICLES(v) {
if (v->owner == old_owner && IsCompanyBuildableVehicleType(v->type)) {
if (new_owner == INVALID_OWNER) {
if (v->Previous() == NULL) delete v;
} else {
v->owner = new_owner;
v->colourmap = PAL_NONE;
if (IsEngineCountable(v)) Company::Get(new_owner)->num_engines[v->engine_type]++;
if (v->IsPrimaryVehicle()) v->unitnumber = unitidgen[v->type].NextID();
}
}
}
}
/* Change ownership of tiles */
{
TileIndex tile = 0;
do {
ChangeTileOwner(tile, old_owner, new_owner);
} while (++tile != MapSize());
if (new_owner != INVALID_OWNER) {
/* Update all signals because there can be new segment that was owned by two companies
* and signals were not propagated
* Similiar with crossings - it is needed to bar crossings that weren't before
* because of different owner of crossing and approaching train */
tile = 0;
do {
if (IsTileType(tile, MP_RAILWAY) && IsTileOwner(tile, new_owner) && HasSignals(tile)) {
TrackBits tracks = GetTrackBits(tile);
do { // there may be two tracks with signals for TRACK_BIT_HORZ and TRACK_BIT_VERT
Track track = RemoveFirstTrack(&tracks);
if (HasSignalOnTrack(tile, track)) AddTrackToSignalBuffer(tile, track, new_owner);
} while (tracks != TRACK_BIT_NONE);
} else if (IsLevelCrossingTile(tile) && IsTileOwner(tile, new_owner)) {
UpdateLevelCrossing(tile);
}
} while (++tile != MapSize());
}
/* update signals in buffer */
UpdateSignalsInBuffer();
}
/* convert owner of stations (including deleted ones, but excluding buoys) */
Station *st;
FOR_ALL_STATIONS(st) {
if (st->owner == old_owner) {
/* if a company goes bankrupt, set owner to OWNER_NONE so the sign doesn't disappear immediately
* also, drawing station window would cause reading invalid company's colour */
st->owner = new_owner == INVALID_OWNER ? OWNER_NONE : new_owner;
}
}
/* do the same for waypoints (we need to do this here so deleted waypoints are converted too) */
Waypoint *wp;
FOR_ALL_WAYPOINTS(wp) {
if (wp->owner == old_owner) {
wp->owner = new_owner == INVALID_OWNER ? OWNER_NONE : new_owner;
}
}
/* In all cases clear replace engine rules.
* Even if it was copied, it could interfere with new owner's rules */
RemoveAllEngineReplacementForCompany(Company::Get(old_owner));
if (new_owner == INVALID_OWNER) {
RemoveAllGroupsForCompany(old_owner);
} else {
Group *g;
FOR_ALL_GROUPS(g) {
if (g->owner == old_owner) g->owner = new_owner;
}
}
Sign *si;
FOR_ALL_SIGNS(si) {
if (si->owner == old_owner) si->owner = new_owner == INVALID_OWNER ? OWNER_NONE : new_owner;
}
/* Change colour of existing windows */
if (new_owner != INVALID_OWNER) ChangeWindowOwner(old_owner, new_owner);
_current_company = old;
MarkWholeScreenDirty();
}
static void ChangeNetworkOwner(Owner current_owner, Owner new_owner)
{
#ifdef ENABLE_NETWORK
if (!_networking) return;
if (current_owner == _local_company) {
SetLocalCompany(new_owner);
}
if (!_network_server) return;
NetworkServerChangeOwner(current_owner, new_owner);
#endif /* ENABLE_NETWORK */
}
static void CompanyCheckBankrupt(Company *c)
{
/* If the company has money again, it does not go bankrupt */
if (c->money >= 0) {
c->quarters_of_bankrupcy = 0;
return;
}
c->quarters_of_bankrupcy++;
CompanyNewsInformation *cni = MallocT<CompanyNewsInformation>(1);
cni->FillData(c);
switch (c->quarters_of_bankrupcy) {
case 0:
case 1:
free(cni);
break;
case 2:
SetDParam(0, STR_NEWS_COMPANY_IN_TROUBLE_TITLE);
SetDParam(1, STR_NEWS_COMPANY_IN_TROUBLE_DESCRIPTION);
SetDParamStr(2, cni->company_name);
AddCompanyNewsItem(STR_NEWS_MESSAGE, NS_COMPANY_TROUBLE, cni);
AI::BroadcastNewEvent(new AIEventCompanyInTrouble(c->index));
break;
case 3: {
/* XXX - In multiplayer, should we ask other companies if it wants to take
over when it is a human company? -- TrueLight */
if (!c->is_ai) {
SetDParam(0, STR_NEWS_COMPANY_IN_TROUBLE_TITLE);
SetDParam(1, STR_NEWS_COMPANY_IN_TROUBLE_DESCRIPTION);
SetDParamStr(2, cni->company_name);
AddCompanyNewsItem(STR_NEWS_MESSAGE, NS_COMPANY_TROUBLE, cni);
break;
}
/* Check if the company has any value.. if not, declare it bankrupt
* right now */
Money val = CalculateCompanyValue(c);
if (val > 0) {
c->bankrupt_value = val;
c->bankrupt_asked = 1 << c->index; // Don't ask the owner
c->bankrupt_timeout = 0;
free(cni);
break;
}
/* Else, falltrue to case 4... */
}
default:
case 4:
if (!_networking && _local_company == c->index) {
/* If we are in offline mode, leave the company playing. Eg. there
* is no THE-END, otherwise mark the client as spectator to make sure
* he/she is no long in control of this company. However... when you
* join another company (cheat) the "unowned" company can bankrupt. */
c->bankrupt_asked = MAX_UVALUE(CompanyMask);
c->bankrupt_timeout = 0x456;
break;
}
/* Close everything the owner has open */
DeleteCompanyWindows(c->index);
/* Show bankrupt news */
SetDParam(0, STR_NEWS_COMPANY_BANKRUPT_TITLE);
SetDParam(1, STR_NEWS_COMPANY_BANKRUPT_DESCRIPTION);
SetDParamStr(2, cni->company_name);
AddCompanyNewsItem(STR_NEWS_MESSAGE, NS_COMPANY_BANKRUPT, cni);
/* Remove the company */
ChangeNetworkOwner(c->index, COMPANY_SPECTATOR);
ChangeOwnershipOfCompanyItems(c->index, INVALID_OWNER);
if (c->is_ai) AI::Stop(c->index);
CompanyID c_index = c->index;
delete c;
AI::BroadcastNewEvent(new AIEventCompanyBankrupt(c_index));
}
}
static void CompaniesGenStatistics()
{
Station *st;
Company *c;
FOR_ALL_STATIONS(st) {
_current_company = st->owner;
CommandCost cost(EXPENSES_PROPERTY, _price.station_value >> 1);
SubtractMoneyFromCompany(cost);
}
if (!HasBit(1 << 0 | 1 << 3 | 1 << 6 | 1 << 9, _cur_month))
return;
FOR_ALL_COMPANIES(c) {
memmove(&c->old_economy[1], &c->old_economy[0], sizeof(c->old_economy) - sizeof(c->old_economy[0]));
c->old_economy[0] = c->cur_economy;
memset(&c->cur_economy, 0, sizeof(c->cur_economy));
if (c->num_valid_stat_ent != 24) c->num_valid_stat_ent++;
UpdateCompanyRatingAndValue(c, true);
if (c->block_preview != 0) c->block_preview--;
CompanyCheckBankrupt(c);
}
InvalidateWindow(WC_INCOME_GRAPH, 0);
InvalidateWindow(WC_OPERATING_PROFIT, 0);
InvalidateWindow(WC_DELIVERED_CARGO, 0);
InvalidateWindow(WC_PERFORMANCE_HISTORY, 0);
InvalidateWindow(WC_COMPANY_VALUE, 0);
InvalidateWindow(WC_COMPANY_LEAGUE, 0);
}
static void AddSingleInflation(Money *value, uint16 *frac, int32 amt)
{
/* Is it safe to add inflation ? */
if ((INT64_MAX / amt) < (*value + 1)) {
*value = INT64_MAX / amt;
*frac = 0;
} else {
int64 tmp = (int64)*value * amt + *frac;
*frac = GB(tmp, 0, 16);
*value += tmp >> 16;
}
}
static void AddInflation(bool check_year = true)
{
/* The cargo payment inflation differs from the normal inflation, so the
* relative amount of money you make with a transport decreases slowly over
* the 170 years. After a few hundred years we reach a level in which the
* games will become unplayable as the maximum income will be less than
* the minimum running cost.
*
* Furthermore there are a lot of inflation related overflows all over the
* place. Solving them is hardly possible because inflation will always
* reach the overflow threshold some day. So we'll just perform the
* inflation mechanism during the first 170 years (the amount of years that
* one had in the original TTD) and stop doing the inflation after that
* because it only causes problems that can't be solved nicely and the
* inflation doesn't add anything after that either; it even makes playing
* it impossible due to the diverging cost and income rates.
*/
if (check_year && (_cur_year - _settings_game.game_creation.starting_year) >= (ORIGINAL_MAX_YEAR - ORIGINAL_BASE_YEAR)) return;
/* Approximation for (100 + infl_amount)% ** (1 / 12) - 100%
* scaled by 65536
* 12 -> months per year
* This is only a good approxiamtion for small values
*/
int32 inf = _economy.infl_amount * 54;
for (uint i = 0; i != NUM_PRICES; i++) {
AddSingleInflation((Money*)&_price + i, _price_frac + i, inf);
}
AddSingleInflation(&_economy.max_loan_unround, &_economy.max_loan_unround_fract, inf);
if (_economy.max_loan + 50000 <= _economy.max_loan_unround) _economy.max_loan += 50000;
inf = _economy.infl_amount_pr * 54;
for (CargoID i = 0; i < NUM_CARGO; i++) {
AddSingleInflation(
(Money*)_cargo_payment_rates + i,
_cargo_payment_rates_frac + i,
inf
);
}
InvalidateWindowClasses(WC_BUILD_VEHICLE);
InvalidateWindowClasses(WC_REPLACE_VEHICLE);
InvalidateWindowClasses(WC_VEHICLE_DETAILS);
InvalidateWindow(WC_PAYMENT_RATES, 0);
}
static void CompaniesPayInterest()
{
const Company *c;
FOR_ALL_COMPANIES(c) {
_current_company = c->index;
/* Over a year the paid interest should be "loan * interest percentage",
* but... as that number is likely not dividable by 12 (pay each month),
* one needs to account for that in the monthly fee calculations.
* To easily calculate what one should pay "this" month, you calculate
* what (total) should have been paid up to this month and you substract
* whatever has been paid in the previous months. This will mean one month
* it'll be a bit more and the other it'll be a bit less than the average
* monthly fee, but on average it will be exact. */
Money yearly_fee = c->current_loan * _economy.interest_rate / 100;
Money up_to_previous_month = yearly_fee * _cur_month / 12;
Money up_to_this_month = yearly_fee * (_cur_month + 1) / 12;
SubtractMoneyFromCompany(CommandCost(EXPENSES_LOAN_INT, up_to_this_month - up_to_previous_month));
SubtractMoneyFromCompany(CommandCost(EXPENSES_OTHER, _price.station_value >> 2));
}
}
static void HandleEconomyFluctuations()
{
if (_settings_game.difficulty.economy == 0) return;
if (--_economy.fluct == 0) {
2005-11-15 09:49:46 +01:00
_economy.fluct = -(int)GB(Random(), 0, 2);
AddNewsItem(STR_NEWS_BEGIN_OF_RECESSION, NS_ECONOMY);
} else if (_economy.fluct == -12) {
2005-11-15 09:49:46 +01:00
_economy.fluct = GB(Random(), 0, 8) + 312;
AddNewsItem(STR_NEWS_END_OF_RECESSION, NS_ECONOMY);
}
}
static byte _price_category[NUM_PRICES] = {
0, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 1, 1, 1, 1, 1, 1,
2,
};
static const Money _price_base[NUM_PRICES] = {
100, ///< station_value
100, ///< build_rail
95, ///< build_road
65, ///< build_signals
275, ///< build_bridge
600, ///< build_train_depot
500, ///< build_road_depot
700, ///< build_ship_depot
450, ///< build_tunnel
200, ///< train_station_track
180, ///< train_station_length
600, ///< build_airport
200, ///< build_bus_station
200, ///< build_truck_station
350, ///< build_dock
400000, ///< build_railvehicle
2000, ///< build_railwagon
700000, ///< aircraft_base
14000, ///< roadveh_base
65000, ///< ship_base
20, ///< build_trees
250, ///< terraform
20, ///< clear_grass
40, ///< clear_roughland
200, ///< clear_rocks
500, ///< clear_fields
20, ///< remove_trees
-70, ///< remove_rail
10, ///< remove_signals
50, ///< clear_bridge
80, ///< remove_train_depot
80, ///< remove_road_depot
90, ///< remove_ship_depot
30, ///< clear_tunnel
10000, ///< clear_water
50, ///< remove_rail_station
30, ///< remove_airport
50, ///< remove_bus_station
50, ///< remove_truck_station
55, ///< remove_dock
1600, ///< remove_house
40, ///< remove_road
5600, ///< running_rail[0] steam
5200, ///< running_rail[1] diesel
4800, ///< running_rail[2] electric
9600, ///< aircraft_running
1600, ///< roadveh_running
5600, ///< ship_running
1000000, ///< build_industry
};
static byte price_base_multiplier[NUM_PRICES];
/**
* Reset changes to the price base multipliers.
*/
void ResetPriceBaseMultipliers()
{
uint i;
/* 8 means no multiplier. */
for (i = 0; i < NUM_PRICES; i++)
price_base_multiplier[i] = 8;
}
/**
* Change a price base by the given factor.
* The price base is altered by factors of two, with an offset of 8.
* NewBaseCost = OldBaseCost * 2^(n-8)
* @param price Index of price base to change.
* @param factor Amount to change by.
*/
void SetPriceBaseMultiplier(uint price, byte factor)
{
assert(price < NUM_PRICES);
price_base_multiplier[price] = factor;
}
/**
* Initialize the variables that will maintain the daily industry change system.
* @param init_counter specifies if the counter is required to be initialized
*/
void StartupIndustryDailyChanges(bool init_counter)
{
uint map_size = MapLogX() + MapLogY();
/* After getting map size, it needs to be scaled appropriately and divided by 31,
* which stands for the days in a month.
* Using just 31 will make it so that a monthly reset (based on the real number of days of that month)
* would not be needed.
* Since it is based on "fractionnal parts", the leftover days will not make much of a difference
* on the overall total number of changes performed */
_economy.industry_daily_increment = (1 << map_size) / 31;
if (init_counter) {
/* A new game or a savegame from an older version will require the counter to be initialized */
_economy.industry_daily_change_counter = 0;
}
}
void StartupEconomy()
{
int i;
assert(sizeof(_price) == NUM_PRICES * sizeof(Money));
for (i = 0; i != NUM_PRICES; i++) {
Money price = _price_base[i];
if (_price_category[i] != 0) {
uint mod = _price_category[i] == 1 ? _settings_game.difficulty.vehicle_costs : _settings_game.difficulty.construction_cost;
if (mod < 1) {
price = price * 3 >> 2;
} else if (mod > 1) {
price = price * 9 >> 3;
}
}
if (price_base_multiplier[i] > 8) {
price <<= price_base_multiplier[i] - 8;
} else {
price >>= 8 - price_base_multiplier[i];
}
((Money*)&_price)[i] = price;
_price_frac[i] = 0;
}
_economy.interest_rate = _settings_game.difficulty.initial_interest;
_economy.infl_amount = _settings_game.difficulty.initial_interest;
_economy.infl_amount_pr = max(0, _settings_game.difficulty.initial_interest - 1);
_economy.max_loan_unround = _economy.max_loan = _settings_game.difficulty.max_loan;
_economy.fluct = GB(Random(), 0, 8) + 168;
StartupIndustryDailyChanges(true); // As we are starting a new game, initialize the counter too
}
void ResetEconomy()
{
/* Test if resetting the economy is needed. */
bool needed = false;
for (CargoID c = 0; c < NUM_CARGO; c++) {
const CargoSpec *cs = GetCargo(c);
if (!cs->IsValid()) continue;
if (_cargo_payment_rates[c] == 0) {
needed = true;
break;
}
}
if (!needed) return;
/* Remember old unrounded maximum loan value. NewGRF has the ability
* to change all the other inflation affected base costs. */
Money old_value = _economy.max_loan_unround;
/* Reset the economy */
StartupEconomy();
InitializeLandscapeVariables(false);
/* Reapply inflation, ignoring the year */
while (old_value > _economy.max_loan_unround) {
AddInflation(false);
}
}
Money GetPriceByIndex(uint8 index)
{
if (index > NUM_PRICES) return 0;
return ((Money*)&_price)[index];
}
Money GetTransportedGoodsIncome(uint num_pieces, uint dist, byte transit_days, CargoID cargo_type)
{
const CargoSpec *cs = GetCargo(cargo_type);
/* Use callback to calculate cargo profit, if available */
if (HasBit(cs->callback_mask, CBM_CARGO_PROFIT_CALC)) {
uint32 var18 = min(dist, 0xFFFF) | (min(num_pieces, 0xFF) << 16) | (transit_days << 24);
uint16 callback = GetCargoCallback(CBID_CARGO_PROFIT_CALC, 0, var18, cs);
if (callback != CALLBACK_FAILED) {
int result = GB(callback, 0, 14);
/* Simulate a 15 bit signed value */
if (HasBit(callback, 14)) result = 0x4000 - result;
/* "The result should be a signed multiplier that gets multiplied
* by the amount of cargo moved and the price factor, then gets
* divided by 8192." */
return result * num_pieces * _cargo_payment_rates[cargo_type] / 8192;
}
}
/* zero the distance (thus income) if it's the bank and very short transport. */
if (_settings_game.game_creation.landscape == LT_TEMPERATE && cs->label == 'VALU' && dist < 10) return 0;
static const int MIN_TIME_FACTOR = 31;
static const int MAX_TIME_FACTOR = 255;
const int days1 = cs->transit_days[0];
const int days2 = cs->transit_days[1];
const int days_over_days1 = max( transit_days - days1, 0);
const int days_over_days2 = max(days_over_days1 - days2, 0);
/*
* The time factor is calculated based on the time it took
* (transit_days) compared two cargo-depending values. The
* range is divided into three parts:
*
* - constant for fast transits
* - linear decreasing with time with a slope of -1 for medium transports
* - linear decreasing with time with a slope of -2 for slow transports
*
*/
const int time_factor = max(MAX_TIME_FACTOR - days_over_days1 - days_over_days2, MIN_TIME_FACTOR);
return BigMulS(dist * time_factor * num_pieces, _cargo_payment_rates[cargo_type], 21);
}
/**
* @note THIS STRUCTURE WILL BE REMOVED SOON!
*/
struct FindIndustryToDeliverData {
const Rect *rect; ///< Station acceptance rectangle
CargoID cargo_type; ///< Cargo type that was delivered
Industry *ind; ///< Returns found industry
const IndustrySpec *indspec; ///< Spec of ind
uint cargo_index; ///< Index of cargo_type in acceptance list of ind
};
/**
* @note THIS FUNCTION WILL BE REMOVED SOON!
*/
static bool FindIndustryToDeliver(TileIndex ind_tile, void *user_data)
{
FindIndustryToDeliverData *callback_data = (FindIndustryToDeliverData *)user_data;
const Rect *rect = callback_data->rect;
CargoID cargo_type = callback_data->cargo_type;
/* Only process industry tiles */
if (!IsTileType(ind_tile, MP_INDUSTRY)) return false;
/* Only process tiles in the station acceptance rectangle */
int x = TileX(ind_tile);
int y = TileY(ind_tile);
if (x < rect->left || x > rect->right || y < rect->top || y > rect->bottom) return false;
Industry *ind = GetIndustryByTile(ind_tile);
const IndustrySpec *indspec = GetIndustrySpec(ind->type);
uint cargo_index;
for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) {
if (cargo_type == ind->accepts_cargo[cargo_index]) break;
}
/* Check if matching cargo has been found */
if (cargo_index >= lengthof(ind->accepts_cargo)) return false;
/* Check if industry temporarly refuses acceptance */
if (HasBit(indspec->callback_flags, CBM_IND_REFUSE_CARGO)) {
uint16 res = GetIndustryCallback(CBID_INDUSTRY_REFUSE_CARGO, 0, GetReverseCargoTranslation(cargo_type, indspec->grf_prop.grffile), ind, ind->type, ind->xy);
if (res == 0) return false;
}
/* Found industry accepting the cargo */
callback_data->ind = ind;
callback_data->indspec = indspec;
callback_data->cargo_index = cargo_index;
return true;
}
/**
* Transfer goods from station to industry.
* All cargo is delivered to the nearest (Manhattan) industry to the station sign, which is inside the acceptance rectangle and actually accepts the cargo.
* @param st The station that accepted the cargo
* @param cargo_type Type of cargo delivered
* @param nun_pieces Amount of cargo delivered
* @note THIS FUNCTION WILL BE REMOVED SOON!
*/
static Industry *DeliverGoodsToIndustryCheckOldStyle(const Station *st, CargoID cargo_type, int num_pieces)
{
if (st->rect.IsEmpty()) return NULL;
/* Compute acceptance rectangle */
int catchment_radius = st->GetCatchmentRadius();
Rect rect = {
max<int>(st->rect.left - catchment_radius, 0),
max<int>(st->rect.top - catchment_radius, 0),
min<int>(st->rect.right + catchment_radius, MapMaxX()),
min<int>(st->rect.bottom + catchment_radius, MapMaxY())
};
/* Compute maximum extent of acceptance rectangle wrt. station sign */
TileIndex start_tile = st->xy;
uint max_radius = max(
max(DistanceManhattan(start_tile, TileXY(rect.left , rect.top)), DistanceManhattan(start_tile, TileXY(rect.left , rect.bottom))),
max(DistanceManhattan(start_tile, TileXY(rect.right, rect.top)), DistanceManhattan(start_tile, TileXY(rect.right, rect.bottom)))
);
FindIndustryToDeliverData callback_data;
callback_data.rect = &rect;
callback_data.cargo_type = cargo_type;
callback_data.ind = NULL;
callback_data.indspec = NULL;
callback_data.cargo_index = 0;
/* Find the nearest industrytile to the station sign inside the catchment area, whose industry accepts the cargo.
* This fails in three cases:
* 1) The station accepts the cargo because there are enough houses around it accepting the cargo.
* 2) The industries in the catchment area temporarily reject the cargo, and the daily station loop has not yet updated station acceptance.
* 3) The results of callbacks CBID_INDUSTRY_REFUSE_CARGO and CBID_INDTILE_CARGO_ACCEPTANCE are inconsistent. (documented behaviour)
*/
if (CircularTileSearch(&start_tile, 2 * max_radius + 1, FindIndustryToDeliver, &callback_data)) return callback_data.ind;
return NULL;
}
/** The industries we've currently brought cargo to. */
static SmallIndustryList _cargo_delivery_destinations;
/**
* Transfer goods from station to industry.
* All cargo is delivered to the nearest (Manhattan) industry to the station sign, which is inside the acceptance rectangle and actually accepts the cargo.
* @param st The station that accepted the cargo
* @param cargo_type Type of cargo delivered
* @param nun_pieces Amount of cargo delivered
*/
static void DeliverGoodsToIndustry(const Station *st, CargoID cargo_type, int num_pieces)
{
/* Find the nearest industrytile to the station sign inside the catchment area, whose industry accepts the cargo.
* This fails in three cases:
* 1) The station accepts the cargo because there are enough houses around it accepting the cargo.
* 2) The industries in the catchment area temporarily reject the cargo, and the daily station loop has not yet updated station acceptance.
* 3) The results of callbacks CBID_INDUSTRY_REFUSE_CARGO and CBID_INDTILE_CARGO_ACCEPTANCE are inconsistent. (documented behaviour)
*/
for (uint i = 0; i < st->industries_near.Length(); i++) {
Industry *ind = st->industries_near[i];
const IndustrySpec *indspec = GetIndustrySpec(ind->type);
uint cargo_index;
for (cargo_index = 0; cargo_index < lengthof(ind->accepts_cargo); cargo_index++) {
if (cargo_type == ind->accepts_cargo[cargo_index]) break;
}
/* Check if matching cargo has been found */
if (cargo_index >= lengthof(ind->accepts_cargo)) continue;
/* Check if industry temporarily refuses acceptance */
if (HasBit(indspec->callback_flags, CBM_IND_REFUSE_CARGO)) {
uint16 res = GetIndustryCallback(CBID_INDUSTRY_REFUSE_CARGO, 0, GetReverseCargoTranslation(cargo_type, indspec->grf_prop.grffile), ind, ind->type, ind->xy);
if (res == 0) continue;
}
/* Insert the industry into _cargo_delivery_destinations, if not yet contained */
_cargo_delivery_destinations.Include(ind);
assert(DeliverGoodsToIndustryCheckOldStyle(st, cargo_type, num_pieces) == ind); // safety check, will be removed soon
ind->incoming_cargo_waiting[cargo_index] = min(num_pieces + ind->incoming_cargo_waiting[cargo_index], 0xFFFF);
return;
}
assert(DeliverGoodsToIndustryCheckOldStyle(st, cargo_type, num_pieces) == NULL); // safety check, will be removed soon
}
/**
* Delivers goods to industries/towns and calculates the payment
* @param num_pieces amount of cargo delivered
* @param source Originstation of the cargo
* @param dest Station the cargo has been unloaded
* @param source_tile The origin of the cargo for distance calculation
* @param days_in_transit Travel time
* @param company The company delivering the cargo
* The cargo is just added to the stockpile of the industry. It is due to the caller to trigger the industry's production machinery
*/
static Money DeliverGoods(int num_pieces, CargoID cargo_type, StationID source, StationID dest, TileIndex source_tile, byte days_in_transit, Company *company)
{
bool subsidised = false;
assert(num_pieces > 0);
/* Update company statistics */
company->cur_economy.delivered_cargo += num_pieces;
SetBit(company->cargo_types, cargo_type);
const Station *s_to = Station::Get(dest);
if (source != INVALID_STATION) {
const Station *s_from = Station::Get(source);
/* Check if a subsidy applies. */
subsidised = CheckSubsidised(s_from, s_to, cargo_type);
}
/* Increase town's counter for some special goods types */
const CargoSpec *cs = GetCargo(cargo_type);
if (cs->town_effect == TE_FOOD) s_to->town->new_act_food += num_pieces;
if (cs->town_effect == TE_WATER) s_to->town->new_act_water += num_pieces;
/* Give the goods to the industry. */
DeliverGoodsToIndustry(s_to, cargo_type, num_pieces);
/* Determine profit */
Money profit = GetTransportedGoodsIncome(num_pieces, DistanceManhattan(source_tile, s_to->xy), days_in_transit, cargo_type);
/* Modify profit if a subsidy is in effect */
if (subsidised) {
switch (_settings_game.difficulty.subsidy_multiplier) {
case 0: profit += profit >> 1; break;
case 1: profit *= 2; break;
case 2: profit *= 3; break;
default: profit *= 4; break;
}
}
return profit;
}
/**
* Inform the industry about just delivered cargo
* DeliverGoodsToIndustry() silently incremented incoming_cargo_waiting, now it is time to do something with the new cargo.
* @param i The industry to process
*/
static void TriggerIndustryProduction(Industry *i)
{
const IndustrySpec *indspec = GetIndustrySpec(i->type);
uint16 callback = indspec->callback_flags;
i->was_cargo_delivered = true;
i->last_cargo_accepted_at = _date;
if (HasBit(callback, CBM_IND_PRODUCTION_CARGO_ARRIVAL) || HasBit(callback, CBM_IND_PRODUCTION_256_TICKS)) {
if (HasBit(callback, CBM_IND_PRODUCTION_CARGO_ARRIVAL)) {
IndustryProductionCallback(i, 0);
} else {
InvalidateWindow(WC_INDUSTRY_VIEW, i->index);
}
} else {
for (uint cargo_index = 0; cargo_index < lengthof(i->incoming_cargo_waiting); cargo_index++) {
uint cargo_waiting = i->incoming_cargo_waiting[cargo_index];
if (cargo_waiting == 0) continue;
i->produced_cargo_waiting[0] = min(i->produced_cargo_waiting[0] + (cargo_waiting * indspec->input_cargo_multiplier[cargo_index][0] / 256), 0xFFFF);
i->produced_cargo_waiting[1] = min(i->produced_cargo_waiting[1] + (cargo_waiting * indspec->input_cargo_multiplier[cargo_index][1] / 256), 0xFFFF);
i->incoming_cargo_waiting[cargo_index] = 0;
}
}
TriggerIndustry(i, INDUSTRY_TRIGGER_RECEIVED_CARGO);
StartStopIndustryTileAnimation(i, IAT_INDUSTRY_RECEIVED_CARGO);
}
/**
* Performs the vehicle payment _and_ marks the vehicle to be unloaded.
* @param front_v the vehicle to be unloaded
*/
void VehiclePayment(Vehicle *front_v)
{
int result = 0;
Money vehicle_profit = 0; // Money paid to the train
Money route_profit = 0; // The grand total amount for the route. A-D of transfer chain A-B-C-D
Money virtual_profit = 0; // The virtual profit for entire vehicle chain
StationID last_visited = front_v->last_station_visited;
Station *st = Station::Get(last_visited);
Company *company = Company::Get(front_v->owner);
/* The owner of the train wants to be paid */
CompanyID old_company = _current_company;
_current_company = front_v->owner;
/* At this moment loading cannot be finished */
ClrBit(front_v->vehicle_flags, VF_LOADING_FINISHED);
/* Start unloading in at the first possible moment */
front_v->load_unload_time_rem = 1;
for (Vehicle *v = front_v; v != NULL; v = v->Next()) {
/* No cargo to unload */
if (v->cargo_cap == 0 || v->cargo.Empty() || (front_v->current_order.GetUnloadType() & OUFB_NO_UNLOAD)) continue;
/* All cargo has already been paid for, no need to pay again */
if (!v->cargo.UnpaidCargo()) {
SetBit(v->vehicle_flags, VF_CARGO_UNLOADING);
continue;
}
GoodsEntry *ge = &st->goods[v->cargo_type];
const CargoList::List *cargos = v->cargo.Packets();
for (CargoList::List::const_iterator it = cargos->begin(); it != cargos->end(); it++) {
CargoPacket *cp = *it;
if (!cp->paid_for &&
cp->source != last_visited &&
HasBit(ge->acceptance_pickup, GoodsEntry::ACCEPTANCE) &&
(front_v->current_order.GetUnloadType() & OUFB_TRANSFER) == 0) {
/* Deliver goods to the station */
st->time_since_unload = 0;
/* handle end of route payment */
Money profit = DeliverGoods(cp->count, v->cargo_type, cp->source, last_visited, cp->source_xy, cp->days_in_transit, company);
cp->paid_for = true;
route_profit += profit; // display amount paid for final route delivery, A-D of a chain A-B-C-D
vehicle_profit += profit - cp->feeder_share; // whole vehicle is not payed for transfers picked up earlier
result |= 1;
SetBit(v->vehicle_flags, VF_CARGO_UNLOADING);
} else if (front_v->current_order.GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) {
if (!cp->paid_for && (front_v->current_order.GetUnloadType() & OUFB_TRANSFER) != 0) {
Money profit = GetTransportedGoodsIncome(
cp->count,
/* pay transfer vehicle for only the part of transfer it has done: ie. cargo_loaded_at_xy to here */
DistanceManhattan(cp->loaded_at_xy, Station::Get(last_visited)->xy),
cp->days_in_transit,
v->cargo_type);
front_v->profit_this_year += profit << 8;
virtual_profit += profit; // accumulate transfer profits for whole vehicle
cp->feeder_share += profit; // account for the (virtual) profit already made for the cargo packet
cp->paid_for = true; // record that the cargo has been paid for to eliminate double counting
}
result |= 2;
SetBit(v->vehicle_flags, VF_CARGO_UNLOADING);
}
}
v->cargo.InvalidateCache();
}
/* Call the production machinery of industries */
const Industry * const *isend = _cargo_delivery_destinations.End();
for (Industry **iid = _cargo_delivery_destinations.Begin(); iid != isend; iid++) {
TriggerIndustryProduction(*iid);
}
_cargo_delivery_destinations.Clear();
if (virtual_profit > 0) {
ShowFeederIncomeAnimation(front_v->x_pos, front_v->y_pos, front_v->z_pos, virtual_profit);
}
if (route_profit != 0) {
front_v->profit_this_year += vehicle_profit << 8;
SubtractMoneyFromCompany(CommandCost(front_v->GetExpenseType(true), -route_profit));
if (IsLocalCompany() && !PlayVehicleSound(front_v, VSE_LOAD_UNLOAD)) {
SndPlayVehicleFx(SND_14_CASHTILL, front_v);
}
ShowCostOrIncomeAnimation(front_v->x_pos, front_v->y_pos, front_v->z_pos, -vehicle_profit);
}
_current_company = old_company;
}
/**
* Loads/unload the vehicle if possible.
* @param v the vehicle to be (un)loaded
* @param cargo_left the amount of each cargo type that is
* virtually left on the platform to be
* picked up by another vehicle when all
* previous vehicles have loaded.
*/
static void LoadUnloadVehicle(Vehicle *v, int *cargo_left)
{
assert(v->current_order.IsType(OT_LOADING));
/* We have not waited enough time till the next round of loading/unloading */
if (--v->load_unload_time_rem != 0) {
if (_settings_game.order.improved_load && (v->current_order.GetLoadType() & OLFB_FULL_LOAD)) {
/* 'Reserve' this cargo for this vehicle, because we were first. */
for (; v != NULL; v = v->Next()) {
int cap_left = v->cargo_cap - v->cargo.Count();
if (cap_left > 0) cargo_left[v->cargo_type] -= cap_left;
}
}
return;
}
StationID last_visited = v->last_station_visited;
Station *st = Station::Get(last_visited);
if (v->type == VEH_TRAIN && (!IsTileType(v->tile, MP_STATION) || GetStationIndex(v->tile) != st->index)) {
/* The train reversed in the station. Take the "easy" way
* out and let the train just leave as it always did. */
SetBit(v->vehicle_flags, VF_LOADING_FINISHED);
return;
}
int unloading_time = 0;
Vehicle *u = v;
int result = 0;
bool completely_emptied = true;
bool anything_unloaded = false;
bool anything_loaded = false;
uint32 cargo_not_full = 0;
uint32 cargo_full = 0;
v->cur_speed = 0;
for (; v != NULL; v = v->Next()) {
if (v->cargo_cap == 0) continue;
byte load_amount = EngInfo(v->engine_type)->load_amount;
/* The default loadamount for mail is 1/4 of the load amount for passengers */
if (v->type == VEH_AIRCRAFT && !IsNormalAircraft(v)) load_amount = (load_amount + 3) / 4;
if (_settings_game.order.gradual_loading && HasBit(EngInfo(v->engine_type)->callbackmask, CBM_VEHICLE_LOAD_AMOUNT)) {
uint16 cb_load_amount = GetVehicleCallback(CBID_VEHICLE_LOAD_AMOUNT, 0, 0, v->engine_type, v);
if (cb_load_amount != CALLBACK_FAILED && GB(cb_load_amount, 0, 8) != 0) load_amount = GB(cb_load_amount, 0, 8);
}
GoodsEntry *ge = &st->goods[v->cargo_type];
if (HasBit(v->vehicle_flags, VF_CARGO_UNLOADING) && (u->current_order.GetUnloadType() & OUFB_NO_UNLOAD) == 0) {
uint cargo_count = v->cargo.Count();
uint amount_unloaded = _settings_game.order.gradual_loading ? min(cargo_count, load_amount) : cargo_count;
bool remaining = false; // Are there cargo entities in this vehicle that can still be unloaded here?
bool accepted = false; // Is the cargo accepted by the station?
if (HasBit(ge->acceptance_pickup, GoodsEntry::ACCEPTANCE) && !(u->current_order.GetUnloadType() & OUFB_TRANSFER)) {
/* The cargo has reached it's final destination, the packets may now be destroyed */
remaining = v->cargo.MoveTo(NULL, amount_unloaded, CargoList::MTA_FINAL_DELIVERY, last_visited);
result |= 1;
accepted = true;
}
/* The !accepted || v->cargo.Count == cargo_count clause is there
* to make it possible to force unload vehicles at the station where
* they were loaded, but to not force unload the vehicle when the
* station is still accepting the cargo in the vehicle. It doesn't
* accept cargo that was loaded at the same station. */
if ((u->current_order.GetUnloadType() & (OUFB_UNLOAD | OUFB_TRANSFER)) && (!accepted || v->cargo.Count() == cargo_count)) {
remaining = v->cargo.MoveTo(&ge->cargo, amount_unloaded);
SetBit(ge->acceptance_pickup, GoodsEntry::PICKUP);
result |= 2;
} else if (!accepted) {
/* The order changed while unloading (unset unload/transfer) or the
* station does not accept goods anymore. */
ClrBit(v->vehicle_flags, VF_CARGO_UNLOADING);
continue;
}
/* Deliver goods to the station */
st->time_since_unload = 0;
unloading_time += amount_unloaded;
anything_unloaded = true;
if (_settings_game.order.gradual_loading && remaining) {
completely_emptied = false;
} else {
/* We have finished unloading (cargo count == 0) */
ClrBit(v->vehicle_flags, VF_CARGO_UNLOADING);
}
continue;
}
/* Do not pick up goods when we have no-load set. */
if (u->current_order.GetLoadType() & OLFB_NO_LOAD) continue;
/* update stats */
int t;
switch (u->type) {
case VEH_TRAIN: t = Train::From(u)->tcache.cached_max_speed; break;
case VEH_ROAD: t = u->max_speed / 2; break;
default: t = u->max_speed; break;
}
/* if last speed is 0, we treat that as if no vehicle has ever visited the station. */
ge->last_speed = min(t, 255);
ge->last_age = _cur_year - u->build_year;
ge->days_since_pickup = 0;
/* If there's goods waiting at the station, and the vehicle
* has capacity for it, load it on the vehicle. */
int cap_left = v->cargo_cap - v->cargo.Count();
if (!ge->cargo.Empty() && cap_left > 0) {
uint cap = cap_left;
uint count = ge->cargo.Count();
/* Skip loading this vehicle if another train/vehicle is already handling
* the same cargo type at this station */
if (_settings_game.order.improved_load && cargo_left[v->cargo_type] <= 0) {
SetBit(cargo_not_full, v->cargo_type);
continue;
}
if (cap > count) cap = count;
if (_settings_game.order.gradual_loading) cap = min(cap, load_amount);
if (_settings_game.order.improved_load) {
/* Don't load stuff that is already 'reserved' for other vehicles */
cap = min((uint)cargo_left[v->cargo_type], cap);
cargo_left[v->cargo_type] -= cap;
}
if (v->cargo.Empty()) TriggerVehicle(v, VEHICLE_TRIGGER_NEW_CARGO);
/* TODO: Regarding this, when we do gradual loading, we
* should first unload all vehicles and then start
* loading them. Since this will cause
* VEHICLE_TRIGGER_EMPTY to be called at the time when
* the whole vehicle chain is really totally empty, the
* completely_emptied assignment can then be safely
* removed; that's how TTDPatch behaves too. --pasky */
completely_emptied = false;
anything_loaded = true;
ge->cargo.MoveTo(&v->cargo, cap, CargoList::MTA_CARGO_LOAD, st->xy);
st->time_since_load = 0;
st->last_vehicle_type = v->type;
StationAnimationTrigger(st, st->xy, STAT_ANIM_CARGO_TAKEN, v->cargo_type);
unloading_time += cap;
result |= 2;
}
if (v->cargo.Count() >= v->cargo_cap) {
SetBit(cargo_full, v->cargo_type);
} else {
SetBit(cargo_not_full, v->cargo_type);
}
}
/* Only set completly_emptied, if we just unloaded all remaining cargo */
completely_emptied &= anything_unloaded;
/* We update these variables here, so gradual loading still fills
* all wagons at the same time instead of using the same 'improved'
* loading algorithm for the wagons (only fill wagon when there is
* enough to fill the previous wagons) */
if (_settings_game.order.improved_load && (u->current_order.GetLoadType() & OLFB_FULL_LOAD)) {
/* Update left cargo */
for (v = u; v != NULL; v = v->Next()) {
int cap_left = v->cargo_cap - v->cargo.Count();
if (cap_left > 0) cargo_left[v->cargo_type] -= cap_left;
}
}
v = u;
if (anything_loaded || anything_unloaded) {
if (_settings_game.order.gradual_loading) {
/* The time it takes to load one 'slice' of cargo or passengers depends
* on the vehicle type - the values here are those found in TTDPatch */
const uint gradual_loading_wait_time[] = { 40, 20, 10, 20 };
unloading_time = gradual_loading_wait_time[v->type];
}
} else {
bool finished_loading = true;
if (v->current_order.GetLoadType() & OLFB_FULL_LOAD) {
if (v->current_order.GetLoadType() == OLF_FULL_LOAD_ANY) {
/* if the aircraft carries passengers and is NOT full, then
* continue loading, no matter how much mail is in */
if ((v->type == VEH_AIRCRAFT && IsCargoInClass(v->cargo_type, CC_PASSENGERS) && v->cargo_cap > v->cargo.Count()) ||
(cargo_not_full && (cargo_full & ~cargo_not_full) == 0)) { // There are stull non-full cargos
finished_loading = false;
}
} else if (cargo_not_full != 0) {
finished_loading = false;
}
}
unloading_time = 20;
SB(v->vehicle_flags, VF_LOADING_FINISHED, 1, finished_loading);
}
if (v->type == VEH_TRAIN) {
/* Each platform tile is worth 2 rail vehicles. */
int overhang = Train::From(v)->tcache.cached_total_length - st->GetPlatformLength(v->tile) * TILE_SIZE;
if (overhang > 0) {
unloading_time <<= 1;
unloading_time += (overhang * unloading_time) / 8;
}
}
/* Calculate the loading indicator fill percent and display
* In the Game Menu do not display indicators
* If _settings_client.gui.loading_indicators == 2, show indicators (bool can be promoted to int as 0 or 1 - results in 2 > 0,1 )
* if _settings_client.gui.loading_indicators == 1, _local_company must be the owner or must be a spectator to show ind., so 1 > 0
* if _settings_client.gui.loading_indicators == 0, do not display indicators ... 0 is never greater than anything
*/
if (_game_mode != GM_MENU && (_settings_client.gui.loading_indicators > (uint)(v->owner != _local_company && _local_company != COMPANY_SPECTATOR))) {
StringID percent_up_down = STR_NULL;
int percent = CalcPercentVehicleFilled(v, &percent_up_down);
if (v->fill_percent_te_id == INVALID_TE_ID) {
v->fill_percent_te_id = ShowFillingPercent(v->x_pos, v->y_pos, v->z_pos + 20, percent, percent_up_down);
} else {
UpdateFillingPercent(v->fill_percent_te_id, percent, percent_up_down);
}
}
v->load_unload_time_rem = unloading_time;
if (completely_emptied) {
TriggerVehicle(v, VEHICLE_TRIGGER_EMPTY);
}
if (result != 0) {
InvalidateWindow(GetWindowClassForVehicleType(v->type), v->owner);
InvalidateWindow(WC_VEHICLE_DETAILS, v->index);
st->MarkTilesDirty(true);
v->MarkDirty();
if (result & 2) InvalidateWindow(WC_STATION_VIEW, last_visited);
}
}
/**
* Load/unload the vehicles in this station according to the order
* they entered.
* @param st the station to do the loading/unloading for
*/
void LoadUnloadStation(Station *st)
{
/* No vehicle is here... */
if (st->loading_vehicles.empty()) return;
int cargo_left[NUM_CARGO];
for (uint i = 0; i < NUM_CARGO; i++) cargo_left[i] = st->goods[i].cargo.Count();
std::list<Vehicle *>::iterator iter;
for (iter = st->loading_vehicles.begin(); iter != st->loading_vehicles.end(); ++iter) {
Vehicle *v = *iter;
if (!(v->vehstatus & (VS_STOPPED | VS_CRASHED))) LoadUnloadVehicle(v, cargo_left);
}
}
void CompaniesMonthlyLoop()
{
CompaniesGenStatistics();
if (_settings_game.economy.inflation) AddInflation();
CompaniesPayInterest();
/* Reset the _current_company flag */
_current_company = OWNER_NONE;
HandleEconomyFluctuations();
}
static void DoAcquireCompany(Company *c)
{
Company *owner;
int i;
Money value;
CompanyID ci = c->index;
CompanyNewsInformation *cni = MallocT<CompanyNewsInformation>(1);
cni->FillData(c, Company::Get(_current_company));
SetDParam(0, STR_NEWS_COMPANY_MERGER_TITLE);
SetDParam(1, c->bankrupt_value == 0 ? STR_NEWS_MERGER_TAKEOVER_TITLE : STR_NEWS_COMPANY_MERGER_DESCRIPTION);
SetDParamStr(2, cni->company_name);
SetDParamStr(3, cni->other_company_name);
SetDParam(4, c->bankrupt_value);
AddCompanyNewsItem(STR_NEWS_MESSAGE, NS_COMPANY_MERGER, cni);
AI::BroadcastNewEvent(new AIEventCompanyMerger(ci, _current_company));
/* original code does this a little bit differently */
ChangeNetworkOwner(ci, _current_company);
ChangeOwnershipOfCompanyItems(ci, _current_company);
if (c->bankrupt_value == 0) {
owner = Company::Get(_current_company);
owner->current_loan += c->current_loan;
}
value = CalculateCompanyValue(c) >> 2;
CompanyID old_company = _current_company;
for (i = 0; i != 4; i++) {
if (c->share_owners[i] != COMPANY_SPECTATOR) {
_current_company = c->share_owners[i];
SubtractMoneyFromCompany(CommandCost(EXPENSES_OTHER, -value));
}
}
_current_company = old_company;
if (c->is_ai) AI::Stop(c->index);
DeleteCompanyWindows(ci);
InvalidateWindowClassesData(WC_TRAINS_LIST, 0);
InvalidateWindowClassesData(WC_SHIPS_LIST, 0);
InvalidateWindowClassesData(WC_ROADVEH_LIST, 0);
InvalidateWindowClassesData(WC_AIRCRAFT_LIST, 0);
delete c;
}
extern int GetAmountOwnedBy(const Company *c, Owner owner);
/** Acquire shares in an opposing company.
* @param tile unused
* @param flags type of operation
* @param p1 company to buy the shares from
* @param p2 unused
*/
CommandCost CmdBuyShareInCompany(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
{
CommandCost cost(EXPENSES_OTHER);
Company *c = Company::GetIfValid(p1);
/* Check if buying shares is allowed (protection against modified clients)
* Cannot buy own shares */
if (c == NULL || !_settings_game.economy.allow_shares || _current_company == (CompanyID)p1) return CMD_ERROR;
/* Protect new companies from hostile takeovers */
if (_cur_year - c->inaugurated_year < 6) return_cmd_error(STR_PROTECTED);
/* Those lines are here for network-protection (clients can be slow) */
if (GetAmountOwnedBy(c, COMPANY_SPECTATOR) == 0) return cost;
/* We can not buy out a real company (temporarily). TODO: well, enable it obviously */
if (GetAmountOwnedBy(c, COMPANY_SPECTATOR) == 1 && !c->is_ai) return cost;
cost.AddCost(CalculateCompanyValue(c) >> 2);
if (flags & DC_EXEC) {
OwnerByte *b = c->share_owners;
int i;
while (*b != COMPANY_SPECTATOR) b++; // share owners is guaranteed to contain at least one COMPANY_SPECTATOR
*b = _current_company;
for (i = 0; c->share_owners[i] == _current_company;) {
if (++i == 4) {
c->bankrupt_value = 0;
DoAcquireCompany(c);
break;
}
}
InvalidateWindow(WC_COMPANY, p1);
}
return cost;
}
/** Sell shares in an opposing company.
* @param tile unused
* @param flags type of operation
* @param p1 company to sell the shares from
* @param p2 unused
*/
CommandCost CmdSellShareInCompany(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
{
Company *c = Company::GetIfValid(p1);
/* Check if selling shares is allowed (protection against modified clients)
* Cannot sell own shares */
if (c == NULL || !_settings_game.economy.allow_shares || _current_company == (CompanyID)p1) return CMD_ERROR;
/* Those lines are here for network-protection (clients can be slow) */
if (GetAmountOwnedBy(c, _current_company) == 0) return CommandCost();
/* adjust it a little to make it less profitable to sell and buy */
Money cost = CalculateCompanyValue(c) >> 2;
cost = -(cost - (cost >> 7));
if (flags & DC_EXEC) {
OwnerByte *b = c->share_owners;
while (*b != _current_company) b++; // share owners is guaranteed to contain company
*b = COMPANY_SPECTATOR;
InvalidateWindow(WC_COMPANY, p1);
}
return CommandCost(EXPENSES_OTHER, cost);
}
/** Buy up another company.
* When a competing company is gone bankrupt you get the chance to purchase
* that company.
* @todo currently this only works for AI companies
* @param tile unused
* @param flags type of operation
* @param p1 company to buy up
* @param p2 unused
*/
CommandCost CmdBuyCompany(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
{
Company *c = Company::GetIfValid(p1);
/* Disable takeovers in multiplayer games */
if (c == NULL || _networking) return CMD_ERROR;
/* Do not allow companies to take over themselves */
if ((CompanyID)p1 == _current_company) return CMD_ERROR;
if (!c->is_ai) return CMD_ERROR;
if (flags & DC_EXEC) {
DoAcquireCompany(c);
}
return CommandCost(EXPENSES_OTHER, c->bankrupt_value);
}