diff options
Diffstat (limited to 'src/economy.cpp')
-rw-r--r-- | src/economy.cpp | 1738 |
1 files changed, 1738 insertions, 0 deletions
diff --git a/src/economy.cpp b/src/economy.cpp new file mode 100644 index 000000000..a325a863c --- /dev/null +++ b/src/economy.cpp @@ -0,0 +1,1738 @@ +/* $Id$ */ + +#include "stdafx.h" +#include "openttd.h" +#include "currency.h" +#include "functions.h" +#include "strings.h" // XXX InjectDParam() +#include "table/strings.h" +#include "table/sprites.h" +#include "map.h" +#include "news.h" +#include "player.h" +#include "station.h" +#include "vehicle.h" +#include "window.h" +#include "gfx.h" +#include "command.h" +#include "saveload.h" +#include "economy.h" +#include "industry.h" +#include "town.h" +#include "network/network.h" +#include "sound.h" +#include "engine.h" +#include "network/network_data.h" +#include "variables.h" +#include "vehicle_gui.h" +#include "ai/ai.h" +#include "train.h" +#include "newgrf_engine.h" +#include "newgrf_sound.h" +#include "newgrf_callbacks.h" +#include "unmovable.h" +#include "date.h" + +// 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_PLAYERS][NUM_SCORE]; + +int64 CalculateCompanyValue(const Player* p) +{ + PlayerID owner = p->index; + int64 value; + + { + Station *st; + uint num = 0; + + FOR_ALL_STATIONS(st) { + if (st->owner == owner) { + uint facil = st->facilities; + do num += (facil&1); while (facil >>= 1); + } + } + + 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 && v->subtype<=2) || + v->type == VEH_Ship) { + value += v->value * 3 >> 1; + } + } + } + + value += p->money64 - p->current_loan; // add real money value + + return max64(value, 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) +int UpdateCompanyRatingAndValue(Player *p, bool update) +{ + byte owner = p->index; + int score = 0; + + memset(_score_part[owner], 0, sizeof(_score_part[owner])); + +/* Count vehicles */ + { + Vehicle *v; + int32 min_profit = 0; + bool min_profit_first = true; + uint num = 0; + + FOR_ALL_VEHICLES(v) { + if (v->owner != owner) continue; + if ((v->type == VEH_Train && IsFrontEngine(v)) || + v->type == VEH_Road || + (v->type == VEH_Aircraft && v->subtype <= 2) || + v->type == VEH_Ship) { + num++; + if (v->age > 730) { + /* Find the vehicle with the lowest amount of profit */ + if (min_profit_first == true) { + min_profit = v->profit_last_year; + min_profit_first = false; + } else if (min_profit > v->profit_last_year) { + min_profit = v->profit_last_year; + } + } + } + } + + _score_part[owner][SCORE_VEHICLES] = num; + /* Don't allow negative min_profit to show */ + if (min_profit > 0) + _score_part[owner][SCORE_MIN_PROFIT] = min_profit; + } + +/* Count stations */ + { + uint num = 0; + const Station* st; + + FOR_ALL_STATIONS(st) { + if (st->owner == owner) { + int facil = st->facilities; + do num += facil&1; while (facil>>=1); + } + } + _score_part[owner][SCORE_STATIONS] = num; + } + +/* Generate statistics depending on recent income statistics */ + { + const PlayerEconomyEntry* pee; + int numec; + int32 min_income; + int32 max_income; + + numec = min(p->num_valid_stat_ent, 12); + if (numec != 0) { + min_income = 0x7FFFFFFF; + max_income = 0; + pee = p->old_economy; + do { + min_income = min(min_income, pee->income + pee->expenses); + max_income = max(max_income, pee->income + pee->expenses); + } while (++pee,--numec); + + if (min_income > 0) + _score_part[owner][SCORE_MIN_INCOME] = min_income; + + _score_part[owner][SCORE_MAX_INCOME] = max_income; + } + } + +/* Generate score depending on amount of transported cargo */ + { + const PlayerEconomyEntry* pee; + int numec; + uint32 total_delivered; + + numec = min(p->num_valid_stat_ent, 4); + if (numec != 0) { + pee = p->old_economy; + total_delivered = 0; + do { + total_delivered += pee->delivered_cargo; + } while (++pee,--numec); + + _score_part[owner][SCORE_DELIVERED] = total_delivered; + } + } + +/* Generate score for variety of cargo */ + { + uint cargo = p->cargo_types; + uint num = 0; + do num += cargo&1; while (cargo>>=1); + _score_part[owner][SCORE_CARGO] = num; + if (update) p->cargo_types = 0; + } + +/* Generate score for player money */ + { + int32 money = p->player_money; + if (money > 0) { + _score_part[owner][SCORE_MONEY] = money; + } + } + +/* Generate score for loan */ + { + _score_part[owner][SCORE_LOAN] = _score_info[SCORE_LOAN].needed - p->current_loan; + } + + // Now we calculate the score for each item.. + { + int i; + int total_score = 0; + int s; + score = 0; + for (i = 0; i < NUM_SCORE; i++) { + // Skip the total + if (i == SCORE_TOTAL) continue; + // Check the score + s = (_score_part[owner][i] >= _score_info[i].needed) ? + _score_info[i].score : + _score_part[owner][i] * _score_info[i].score / _score_info[i].needed; + if (s < 0) s = 0; + 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) { + p->old_economy[0].performance_history = score; + UpdateCompanyHQ(p, score); + p->old_economy[0].company_value = CalculateCompanyValue(p); + } + + InvalidateWindow(WC_PERFORMANCE_DETAIL, 0); + return score; +} + +// use PLAYER_SPECTATOR as new_player to delete the player. +void ChangeOwnershipOfPlayerItems(PlayerID old_player, PlayerID new_player) +{ + Town *t; + PlayerID old = _current_player; + _current_player = old_player; + + /* Temporarily increase the player'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_player == PLAYER_SPECTATOR) { + GetPlayer(old_player)->money64 = MAX_UVALUE(uint64) >>2; // jackpot ;p + UpdatePlayerMoney32(GetPlayer(old_player)); + } + + if (new_player == PLAYER_SPECTATOR) { + Subsidy *s; + + for (s = _subsidies; s != endof(_subsidies); s++) { + if (s->cargo_type != CT_INVALID && s->age >= 12) { + if (GetStation(s->to)->owner == old_player) s->cargo_type = CT_INVALID; + } + } + } + + /* Take care of rating in towns */ + FOR_ALL_TOWNS(t) { + /* If a player takes over, give the ratings to that player. */ + if (new_player != PLAYER_SPECTATOR) { + if (HASBIT(t->have_ratings, old_player)) { + if (HASBIT(t->have_ratings, new_player)) { + // use max of the two ratings. + t->ratings[new_player] = max(t->ratings[new_player], t->ratings[old_player]); + } else { + SETBIT(t->have_ratings, new_player); + t->ratings[new_player] = t->ratings[old_player]; + } + } + } + + /* Reset the ratings for the old player */ + t->ratings[old_player] = 500; + CLRBIT(t->have_ratings, old_player); + } + + { + int num_train = 0; + int num_road = 0; + int num_ship = 0; + int num_aircraft = 0; + Vehicle *v; + + // Determine Ids for the new vehicles + FOR_ALL_VEHICLES(v) { + if (v->owner == new_player) { + switch (v->type) { + case VEH_Train: if (IsFrontEngine(v)) num_train++; break; + case VEH_Road: num_road++; break; + case VEH_Ship: num_ship++; break; + case VEH_Aircraft: if (v->subtype <= 2) num_aircraft++; break; + default: break; + } + } + } + + FOR_ALL_VEHICLES(v) { + if (v->owner == old_player && IS_BYTE_INSIDE(v->type, VEH_Train, VEH_Aircraft + 1)) { + if (new_player == PLAYER_SPECTATOR) { + DeleteWindowById(WC_VEHICLE_VIEW, v->index); + DeleteWindowById(WC_VEHICLE_DETAILS, v->index); + DeleteWindowById(WC_VEHICLE_ORDERS, v->index); + DeleteVehicle(v); + } else { + v->owner = new_player; + if (IsEngineCountable(v)) GetPlayer(new_player)->num_engines[v->engine_type]++; + switch (v->type) { + case VEH_Train: if (IsFrontEngine(v)) v->unitnumber = ++num_train; break; + case VEH_Road: v->unitnumber = ++num_road; break; + case VEH_Ship: v->unitnumber = ++num_ship; break; + case VEH_Aircraft: if (v->subtype <= 2) v->unitnumber = ++num_aircraft; break; + } + } + } + } + } + + // Change ownership of tiles + { + TileIndex tile = 0; + do { + ChangeTileOwner(tile, old_player, new_player); + } while (++tile != MapSize()); + } + + /* Change color of existing windows */ + if (new_player != PLAYER_SPECTATOR) ChangeWindowOwner(old_player, new_player); + + { + Player *p; + uint i; + + /* Check for shares */ + FOR_ALL_PLAYERS(p) { + for (i = 0; i < 4; i++) { + /* 'Sell' the share if this player has any */ + if (p->share_owners[i] == _current_player) { + p->share_owners[i] = PLAYER_SPECTATOR; + } + } + } + p = GetPlayer(_current_player); + /* Sell all the shares that people have on this company */ + for (i = 0; i < 4; i++) + p->share_owners[i] = PLAYER_SPECTATOR; + } + + _current_player = old; + + MarkWholeScreenDirty(); +} + +static void PlayersCheckBankrupt(Player *p) +{ + PlayerID owner; + int64 val; + + // If the player has money again, it does not go bankrupt + if (p->player_money >= 0) { + p->quarters_of_bankrupcy = 0; + return; + } + + p->quarters_of_bankrupcy++; + + owner = p->index; + + switch (p->quarters_of_bankrupcy) { + case 2: + AddNewsItem( (StringID)(owner | NB_BTROUBLE), + NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); + break; + case 3: { + /* XXX - In multiplayer, should we ask other players if it wants to take + over when it is a human company? -- TrueLight */ + if (IsHumanPlayer(owner)) { + AddNewsItem( (StringID)(owner | NB_BTROUBLE), + NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); + break; + } + + // Check if the company has any value.. if not, declare it bankrupt + // right now + val = CalculateCompanyValue(p); + if (val > 0) { + p->bankrupt_value = val; + p->bankrupt_asked = 1 << owner; // Don't ask the owner + p->bankrupt_timeout = 0; + break; + } + // Else, falltrue to case 4... + } + case 4: { + // Close everything the owner has open + DeletePlayerWindows(owner); + +// Show bankrupt news + SetDParam(0, p->name_1); + SetDParam(1, p->name_2); + AddNewsItem( (StringID)(owner | NB_BBANKRUPT), NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); + + if (IsHumanPlayer(owner)) { + /* XXX - If we are in offline mode, leave the player playing. Eg. there + * is no THE-END, otherwise mark the player as spectator to make sure + * he/she is no long in control of this company */ + if (!_networking) { + p->bankrupt_asked = 0xFF; + p->bankrupt_timeout = 0x456; + break; + } else if (owner == _local_player) { + _network_playas = PLAYER_SPECTATOR; + SetLocalPlayer(PLAYER_SPECTATOR); + } + +#ifdef ENABLE_NETWORK + /* The server has to handle all administrative issues, for example + * updating and notifying all clients of what has happened */ + if (_network_server) { + const NetworkClientState *cs; + NetworkClientInfo *ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX); + + /* The server has just gone belly-up, mark it as spectator */ + if (owner == ci->client_playas) { + ci->client_playas = PLAYER_SPECTATOR; + NetworkUpdateClientInfo(NETWORK_SERVER_INDEX); + } + + /* Find all clients that were in control of this company, + * and mark them as spectator; broadcast this message to everyone */ + FOR_ALL_CLIENTS(cs) { + ci = DEREF_CLIENT_INFO(cs); + if (ci->client_playas == owner) { + ci->client_playas = PLAYER_SPECTATOR; + NetworkUpdateClientInfo(ci->client_index); + } + } + } +#endif /* ENABLE_NETWORK */ + } + + /* Remove the player */ + ChangeOwnershipOfPlayerItems(owner, PLAYER_SPECTATOR); + /* Register the player as not-active */ + p->is_active = false; + + if (!IsHumanPlayer(owner) && (!_networking || _network_server) && _ai.enabled) + AI_PlayerDied(owner); + } + } +} + +void DrawNewsBankrupcy(Window *w) +{ + Player *p; + + DrawNewsBorder(w); + + p = GetPlayer(GB(WP(w,news_d).ni->string_id, 0, 4)); + DrawPlayerFace(p->face, p->player_color, 2, 23); + GfxFillRect(3, 23, 3+91, 23+118, 0x323 | USE_COLORTABLE); + + SetDParam(0, p->president_name_1); + SetDParam(1, p->president_name_2); + + DrawStringMultiCenter(49, 148, STR_7058_PRESIDENT, 94); + + switch (WP(w,news_d).ni->string_id & 0xF0) { + case NB_BTROUBLE: + DrawStringCentered(w->width>>1, 1, STR_7056_TRANSPORT_COMPANY_IN_TROUBLE, 0); + + SetDParam(0, p->name_1); + SetDParam(1, p->name_2); + + DrawStringMultiCenter( + ((w->width - 101) >> 1) + 98, + 90, + STR_7057_WILL_BE_SOLD_OFF_OR_DECLARED, + w->width - 101); + break; + + case NB_BMERGER: { + int32 price; + + DrawStringCentered(w->width>>1, 1, STR_7059_TRANSPORT_COMPANY_MERGER, 0); + COPY_IN_DPARAM(0,WP(w,news_d).ni->params, 2); + SetDParam(2, p->name_1); + SetDParam(3, p->name_2); + price = WP(w,news_d).ni->params[2]; + SetDParam(4, price); + DrawStringMultiCenter( + ((w->width - 101) >> 1) + 98, + 90, + price==0 ? STR_707F_HAS_BEEN_TAKEN_OVER_BY : STR_705A_HAS_BEEN_SOLD_TO_FOR, + w->width - 101); + break; + } + + case NB_BBANKRUPT: + DrawStringCentered(w->width>>1, 1, STR_705C_BANKRUPT, 0); + COPY_IN_DPARAM(0,WP(w,news_d).ni->params, 2); + DrawStringMultiCenter( + ((w->width - 101) >> 1) + 98, + 90, + STR_705D_HAS_BEEN_CLOSED_DOWN_BY, + w->width - 101); + break; + + case NB_BNEWCOMPANY: + DrawStringCentered(w->width>>1, 1, STR_705E_NEW_TRANSPORT_COMPANY_LAUNCHED, 0); + SetDParam(0, p->name_1); + SetDParam(1, p->name_2); + COPY_IN_DPARAM(2,WP(w,news_d).ni->params, 2); + DrawStringMultiCenter( + ((w->width - 101) >> 1) + 98, + 90, + STR_705F_STARTS_CONSTRUCTION_NEAR, + w->width - 101); + break; + + default: + NOT_REACHED(); + } +} + +StringID GetNewsStringBankrupcy(const NewsItem *ni) +{ + const Player *p = GetPlayer(GB(ni->string_id, 0, 4)); + + switch (ni->string_id & 0xF0) { + case NB_BTROUBLE: + SetDParam(0, STR_7056_TRANSPORT_COMPANY_IN_TROUBLE); + SetDParam(1, STR_7057_WILL_BE_SOLD_OFF_OR_DECLARED); + SetDParam(2, p->name_1); + SetDParam(3, p->name_2); + return STR_02B6; + case NB_BMERGER: + SetDParam(0, STR_7059_TRANSPORT_COMPANY_MERGER); + SetDParam(1, STR_705A_HAS_BEEN_SOLD_TO_FOR); + COPY_IN_DPARAM(2,ni->params, 2); + SetDParam(4, p->name_1); + SetDParam(5, p->name_2); + COPY_IN_DPARAM(6,ni->params + 2, 1); + return STR_02B6; + case NB_BBANKRUPT: + SetDParam(0, STR_705C_BANKRUPT); + SetDParam(1, STR_705D_HAS_BEEN_CLOSED_DOWN_BY); + COPY_IN_DPARAM(2,ni->params, 2); + return STR_02B6; + case NB_BNEWCOMPANY: + SetDParam(0, STR_705E_NEW_TRANSPORT_COMPANY_LAUNCHED); + SetDParam(1, STR_705F_STARTS_CONSTRUCTION_NEAR); + SetDParam(2, p->name_1); + SetDParam(3, p->name_2); + COPY_IN_DPARAM(4,ni->params, 2); + return STR_02B6; + default: + NOT_REACHED(); + } + + /* useless, but avoids compiler warning this way */ + return 0; +} + +static void PlayersGenStatistics(void) +{ + Station *st; + Player *p; + + FOR_ALL_STATIONS(st) { + _current_player = st->owner; + SET_EXPENSES_TYPE(EXPENSES_PROPERTY); + SubtractMoneyFromPlayer(_price.station_value >> 1); + } + + if (!HASBIT(1<<0|1<<3|1<<6|1<<9, _cur_month)) + return; + + FOR_ALL_PLAYERS(p) { + if (p->is_active) { + memmove(&p->old_economy[1], &p->old_economy[0], sizeof(p->old_economy) - sizeof(p->old_economy[0])); + p->old_economy[0] = p->cur_economy; + memset(&p->cur_economy, 0, sizeof(p->cur_economy)); + + if (p->num_valid_stat_ent != 24) p->num_valid_stat_ent++; + + UpdateCompanyRatingAndValue(p, true); + PlayersCheckBankrupt(p); + + if (p->block_preview != 0) p->block_preview--; + } + } + + 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(int32 *value, uint16 *frac, int32 amt) +{ + int64 tmp; + int32 low; + tmp = BIGMULS(*value, amt); + *frac = (uint16)(low = (uint16)tmp + *frac); + *value += (int32)(tmp >> 16) + (low >> 16); +} + +static void AddInflation(void) +{ + int i; + int32 inf = _economy.infl_amount * 54; + + for (i = 0; i != NUM_PRICES; i++) { + AddSingleInflation((int32*)&_price + i, _price_frac + i, inf); + } + + _economy.max_loan_unround += BIGMULUS(_economy.max_loan_unround, inf, 16); + + if (_economy.max_loan + 50000 <= _economy.max_loan_unround) + _economy.max_loan += 50000; + + inf = _economy.infl_amount_pr * 54; + for (i = 0; i != NUM_CARGO; i++) { + AddSingleInflation( + (int32*)_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 PlayersPayInterest(void) +{ + const Player* p; + int interest = _economy.interest_rate * 54; + + FOR_ALL_PLAYERS(p) { + if (!p->is_active) continue; + + _current_player = p->index; + SET_EXPENSES_TYPE(EXPENSES_LOAN_INT); + + SubtractMoneyFromPlayer(BIGMULUS(p->current_loan, interest, 16)); + + SET_EXPENSES_TYPE(EXPENSES_OTHER); + SubtractMoneyFromPlayer(_price.station_value >> 2); + } +} + +static void HandleEconomyFluctuations(void) +{ + if (_opt.diff.economy == 0) return; + + if (--_economy.fluct == 0) { + _economy.fluct = -(int)GB(Random(), 0, 2); + AddNewsItem(STR_7073_WORLD_RECESSION_FINANCIAL, NEWS_FLAGS(NM_NORMAL,0,NT_ECONOMY,0), 0, 0); + } else if (_economy.fluct == -12) { + _economy.fluct = GB(Random(), 0, 8) + 312; + AddNewsItem(STR_7074_RECESSION_OVER_UPTURN_IN, NEWS_FLAGS(NM_NORMAL,0,NT_ECONOMY,0), 0, 0); + } +} + +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 int32 _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_1 + 40, // purchase_land + 200, // clear_2 + 500, // clear_3 + 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] railroad + 5200, // running_rail[1] monorail + 4800, // running_rail[2] maglev + 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(void) +{ + 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; +} + +void StartupEconomy(void) +{ + int i; + + assert(sizeof(_price) == NUM_PRICES * sizeof(int32)); + + for (i = 0; i != NUM_PRICES; i++) { + int32 price = _price_base[i]; + if (_price_category[i] != 0) { + uint mod = _price_category[i] == 1 ? _opt.diff.vehicle_costs : _opt.diff.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]; + } + ((int32*)&_price)[i] = price; + _price_frac[i] = 0; + } + + _economy.interest_rate = _opt.diff.initial_interest; + _economy.infl_amount = _opt.diff.initial_interest; + _economy.infl_amount_pr = max(0, _opt.diff.initial_interest - 1); + _economy.max_loan_unround = _economy.max_loan = _opt.diff.max_loan * 1000; + _economy.fluct = GB(Random(), 0, 8) + 168; +} + +Pair SetupSubsidyDecodeParam(const Subsidy* s, bool mode) +{ + TileIndex tile; + TileIndex tile2; + Pair tp; + + /* if mode is false, use the singular form */ + SetDParam(0, _cargoc.names_s[s->cargo_type] + (mode ? 0 : 32)); + + if (s->age < 12) { + if (s->cargo_type != CT_PASSENGERS && s->cargo_type != CT_MAIL) { + SetDParam(1, STR_INDUSTRY); + SetDParam(2, s->from); + tile = GetIndustry(s->from)->xy; + + if (s->cargo_type != CT_GOODS && s->cargo_type != CT_FOOD) { + SetDParam(4, STR_INDUSTRY); + SetDParam(5, s->to); + tile2 = GetIndustry(s->to)->xy; + } else { + SetDParam(4, STR_TOWN); + SetDParam(5, s->to); + tile2 = GetTown(s->to)->xy; + } + } else { + SetDParam(1, STR_TOWN); + SetDParam(2, s->from); + tile = GetTown(s->from)->xy; + + SetDParam(4, STR_TOWN); + SetDParam(5, s->to); + tile2 = GetTown(s->to)->xy; + } + } else { + SetDParam(1, s->from); + tile = GetStation(s->from)->xy; + + SetDParam(2, s->to); + tile2 = GetStation(s->to)->xy; + } + + tp.a = tile; + tp.b = tile2; + + return tp; +} + +void DeleteSubsidyWithTown(TownID index) +{ + Subsidy *s; + + for (s = _subsidies; s != endof(_subsidies); s++) { + if (s->cargo_type != CT_INVALID && s->age < 12 && + (((s->cargo_type == CT_PASSENGERS || s->cargo_type == CT_MAIL) && (index == s->from || index == s->to)) || + ((s->cargo_type == CT_GOODS || s->cargo_type == CT_FOOD) && index == s->to))) { + s->cargo_type = CT_INVALID; + } + } +} + +void DeleteSubsidyWithIndustry(IndustryID index) +{ + Subsidy *s; + + for (s = _subsidies; s != endof(_subsidies); s++) { + if (s->cargo_type != CT_INVALID && s->age < 12 && + s->cargo_type != CT_PASSENGERS && s->cargo_type != CT_MAIL && + (index == s->from || (s->cargo_type != CT_GOODS && s->cargo_type != CT_FOOD && index == s->to))) { + s->cargo_type = CT_INVALID; + } + } +} + +void DeleteSubsidyWithStation(StationID index) +{ + Subsidy *s; + bool dirty = false; + + for (s = _subsidies; s != endof(_subsidies); s++) { + if (s->cargo_type != CT_INVALID && s->age >= 12 && + (s->from == index || s->to == index)) { + s->cargo_type = CT_INVALID; + dirty = true; + } + } + + if (dirty) + InvalidateWindow(WC_SUBSIDIES_LIST, 0); +} + +typedef struct FoundRoute { + uint distance; + CargoID cargo; + void *from; + void *to; +} FoundRoute; + +static void FindSubsidyPassengerRoute(FoundRoute *fr) +{ + Town *from,*to; + + fr->distance = (uint)-1; + + fr->from = from = GetRandomTown(); + if (from == NULL || from->population < 400) return; + + fr->to = to = GetRandomTown(); + if (from == to || to == NULL || to->population < 400 || to->pct_pass_transported > 42) + return; + + fr->distance = DistanceManhattan(from->xy, to->xy); +} + +static void FindSubsidyCargoRoute(FoundRoute *fr) +{ + Industry *i; + int trans, total; + CargoID cargo; + + fr->distance = (uint)-1; + + fr->from = i = GetRandomIndustry(); + if (i == NULL) return; + + // Randomize cargo type + if (Random()&1 && i->produced_cargo[1] != CT_INVALID) { + cargo = i->produced_cargo[1]; + trans = i->pct_transported[1]; + total = i->total_production[1]; + } else { + cargo = i->produced_cargo[0]; + trans = i->pct_transported[0]; + total = i->total_production[0]; + } + + // Quit if no production in this industry + // or if the cargo type is passengers + // or if the pct transported is already large enough + if (total == 0 || trans > 42 || cargo == CT_INVALID || cargo == CT_PASSENGERS) + return; + + fr->cargo = cargo; + + if (cargo == CT_GOODS || cargo == CT_FOOD) { + // The destination is a town + Town *t = GetRandomTown(); + + // Only want big towns + if (t == NULL || t->population < 900) return; + + fr->distance = DistanceManhattan(i->xy, t->xy); + fr->to = t; + } else { + // The destination is an industry + Industry *i2 = GetRandomIndustry(); + + // The industry must accept the cargo + if (i == i2 || i == NULL || + (cargo != i2->accepts_cargo[0] && + cargo != i2->accepts_cargo[1] && + cargo != i2->accepts_cargo[2])) + return; + fr->distance = DistanceManhattan(i->xy, i2->xy); + fr->to = i2; + } +} + +static bool CheckSubsidyDuplicate(Subsidy *s) +{ + const Subsidy* ss; + + for (ss = _subsidies; ss != endof(_subsidies); ss++) { + if (s != ss && + ss->from == s->from && + ss->to == s->to && + ss->cargo_type == s->cargo_type) { + s->cargo_type = CT_INVALID; + return true; + } + } + return false; +} + + +static void SubsidyMonthlyHandler(void) +{ + Subsidy *s; + Pair pair; + Station *st; + uint n; + FoundRoute fr; + bool modified = false; + + for (s = _subsidies; s != endof(_subsidies); s++) { + if (s->cargo_type == CT_INVALID) continue; + + if (s->age == 12-1) { + pair = SetupSubsidyDecodeParam(s, 1); + AddNewsItem(STR_202E_OFFER_OF_SUBSIDY_EXPIRED, NEWS_FLAGS(NM_NORMAL, NF_TILE, NT_SUBSIDIES, 0), pair.a, pair.b); + s->cargo_type = CT_INVALID; + modified = true; + } else if (s->age == 2*12-1) { + st = GetStation(s->to); + if (st->owner == _local_player) { + pair = SetupSubsidyDecodeParam(s, 1); + AddNewsItem(STR_202F_SUBSIDY_WITHDRAWN_SERVICE, NEWS_FLAGS(NM_NORMAL, NF_TILE, NT_SUBSIDIES, 0), pair.a, pair.b); + } + s->cargo_type = CT_INVALID; + modified = true; + } else { + s->age++; + } + } + + // 25% chance to go on + if (CHANCE16(1,4)) { + // Find a free slot + s = _subsidies; + while (s->cargo_type != CT_INVALID) { + if (++s == endof(_subsidies)) + goto no_add; + } + + n = 1000; + do { + FindSubsidyPassengerRoute(&fr); + if (fr.distance <= 70) { + s->cargo_type = CT_PASSENGERS; + s->from = ((Town*)fr.from)->index; + s->to = ((Town*)fr.to)->index; + goto add_subsidy; + } + FindSubsidyCargoRoute(&fr); + if (fr.distance <= 70) { + s->cargo_type = fr.cargo; + s->from = ((Industry*)fr.from)->index; + s->to = (fr.cargo == CT_GOODS || fr.cargo == CT_FOOD) ? ((Town*)fr.to)->index : ((Industry*)fr.to)->index; + add_subsidy: + if (!CheckSubsidyDuplicate(s)) { + s->age = 0; + pair = SetupSubsidyDecodeParam(s, 0); + AddNewsItem(STR_2030_SERVICE_SUBSIDY_OFFERED, NEWS_FLAGS(NM_NORMAL, NF_TILE, NT_SUBSIDIES, 0), pair.a, pair.b); + modified = true; + break; + } + } + } while (n--); + } +no_add:; + if (modified) + InvalidateWindow(WC_SUBSIDIES_LIST, 0); +} + +static const SaveLoad _subsidies_desc[] = { + SLE_VAR(Subsidy, cargo_type, SLE_UINT8), + SLE_VAR(Subsidy, age, SLE_UINT8), + SLE_CONDVAR(Subsidy, from, SLE_FILE_U8 | SLE_VAR_U16, 0, 4), + SLE_CONDVAR(Subsidy, from, SLE_UINT16, 5, SL_MAX_VERSION), + SLE_CONDVAR(Subsidy, to, SLE_FILE_U8 | SLE_VAR_U16, 0, 4), + SLE_CONDVAR(Subsidy, to, SLE_UINT16, 5, SL_MAX_VERSION), + SLE_END() +}; + +static void Save_SUBS(void) +{ + int i; + Subsidy *s; + + for (i = 0; i != lengthof(_subsidies); i++) { + s = &_subsidies[i]; + if (s->cargo_type != CT_INVALID) { + SlSetArrayIndex(i); + SlObject(s, _subsidies_desc); + } + } +} + +static void Load_SUBS(void) +{ + int index; + while ((index = SlIterateArray()) != -1) + SlObject(&_subsidies[index], _subsidies_desc); +} + +int32 GetTransportedGoodsIncome(uint num_pieces, uint dist, byte transit_days, CargoID cargo_type) +{ + CargoID cargo = cargo_type; + byte f; + + /* zero the distance if it's the bank and very short transport. */ + if (_opt.landscape == LT_NORMAL && cargo == CT_VALUABLES && dist < 10) + dist = 0; + + f = 255; + if (transit_days > _cargoc.transit_days_1[cargo]) { + transit_days -= _cargoc.transit_days_1[cargo]; + f -= transit_days; + + if (transit_days > _cargoc.transit_days_2[cargo]) { + transit_days -= _cargoc.transit_days_2[cargo]; + + if (f < transit_days) { + f = 0; + } else { + f -= transit_days; + } + } + } + if (f < 31) f = 31; + + return BIGMULSS(dist * f * num_pieces, _cargo_payment_rates[cargo], 21); +} + +static void DeliverGoodsToIndustry(TileIndex xy, CargoID cargo_type, int num_pieces) +{ + Industry* best = NULL; + Industry* ind; + uint u; + + // Check if there's an industry close to the station that accepts the cargo + // XXX - Think of something better to + // 1) Only deliver to industries which are withing the catchment radius + // 2) Distribute between industries if more then one is present + u = (_patches.station_spread + 8) * 2; + FOR_ALL_INDUSTRIES(ind) { + uint t; + + if (( cargo_type == ind->accepts_cargo[0] || + cargo_type == ind->accepts_cargo[1] || + cargo_type == ind->accepts_cargo[2] + ) && + ind->produced_cargo[0] != CT_INVALID && + ind->produced_cargo[0] != cargo_type && + (t = DistanceManhattan(ind->xy, xy)) < u) { + u = t; + best = ind; + } + } + + /* Found one? */ + if (best != NULL) { + best->was_cargo_delivered = true; + best->cargo_waiting[0] = min(best->cargo_waiting[0] + num_pieces, 0xFFFF); + } +} + +static bool CheckSubsidised(Station *from, Station *to, CargoID cargo_type) +{ + Subsidy *s; + TileIndex xy; + Pair pair; + Player *p; + + // check if there is an already existing subsidy that applies to us + for (s = _subsidies; s != endof(_subsidies); s++) { + if (s->cargo_type == cargo_type && + s->age >= 12 && + s->from == from->index && + s->to == to->index) { + return true; + } + } + + /* check if there's a new subsidy that applies.. */ + for (s = _subsidies; s != endof(_subsidies); s++) { + if (s->cargo_type == cargo_type && s->age < 12) { + /* Check distance from source */ + if (cargo_type == CT_PASSENGERS || cargo_type == CT_MAIL) { + xy = GetTown(s->from)->xy; + } else { + xy = (GetIndustry(s->from))->xy; + } + if (DistanceMax(xy, from->xy) > 9) continue; + + /* Check distance from dest */ + switch (cargo_type) { + case CT_PASSENGERS: + case CT_MAIL: + case CT_GOODS: + case CT_FOOD: + xy = GetTown(s->to)->xy; + break; + + default: + xy = GetIndustry(s->to)->xy; + break; + } + if (DistanceMax(xy, to->xy) > 9) continue; + + /* Found a subsidy, change the values to indicate that it's in use */ + s->age = 12; + s->from = from->index; + s->to = to->index; + + /* Add a news item */ + pair = SetupSubsidyDecodeParam(s, 0); + InjectDParam(2); + + p = GetPlayer(_current_player); + SetDParam(0, p->name_1); + SetDParam(1, p->name_2); + AddNewsItem( + STR_2031_SERVICE_SUBSIDY_AWARDED + _opt.diff.subsidy_multiplier, + NEWS_FLAGS(NM_NORMAL, NF_TILE, NT_SUBSIDIES, 0), + pair.a, pair.b + ); + + InvalidateWindow(WC_SUBSIDIES_LIST, 0); + return true; + } + } + return false; +} + +static int32 DeliverGoods(int num_pieces, CargoID cargo_type, StationID source, StationID dest, byte days_in_transit) +{ + bool subsidised; + Station *s_from, *s_to; + int32 profit; + + assert(num_pieces > 0); + + // Update player statistics + { + Player *p = GetPlayer(_current_player); + p->cur_economy.delivered_cargo += num_pieces; + SETBIT(p->cargo_types, cargo_type); + } + + // Get station pointers. + s_from = GetStation(source); + s_to = GetStation(dest); + + // Check if a subsidy applies. + subsidised = CheckSubsidised(s_from, s_to, cargo_type); + + // Increase town's counter for some special goods types + if (cargo_type == CT_FOOD) s_to->town->new_act_food += num_pieces; + if (cargo_type == CT_WATER) s_to->town->new_act_water += num_pieces; + + // Give the goods to the industry. + DeliverGoodsToIndustry(s_to->xy, cargo_type, num_pieces); + + // Determine profit + profit = GetTransportedGoodsIncome(num_pieces, DistanceManhattan(s_from->xy, s_to->xy), days_in_transit, cargo_type); + + // Modify profit if a subsidy is in effect + if (subsidised) { + switch (_opt.diff.subsidy_multiplier) { + case 0: profit += profit >> 1; break; + case 1: profit *= 2; break; + case 2: profit *= 3; break; + default: profit *= 4; break; + } + } + + return profit; +} + +/* + * Returns true if Vehicle v should wait loading because other vehicle is + * already loading the same cargo type + * v = vehicle to load, u = GetFirstInChain(v) + */ +static bool LoadWait(const Vehicle* v, const Vehicle* u) +{ + const Vehicle *w; + const Vehicle *x; + bool has_any_cargo = false; + + if (!(u->current_order.flags & OF_FULL_LOAD)) return false; + + for (w = u; w != NULL; w = w->next) { + if (w->cargo_count != 0) { + if (v->cargo_type == w->cargo_type && + u->last_station_visited == w->cargo_source) { + return false; + } + has_any_cargo = true; + } + } + + FOR_ALL_VEHICLES(x) { + if ((x->type != VEH_Train || IsFrontEngine(x)) && // for all locs + u->last_station_visited == x->last_station_visited && // at the same station + !(x->vehstatus & (VS_STOPPED | VS_CRASHED)) && // not stopped or crashed + x->current_order.type == OT_LOADING && // loading + u != x) { // not itself + bool other_has_any_cargo = false; + bool has_space_for_same_type = false; + bool other_has_same_type = false; + + for (w = x; w != NULL; w = w->next) { + if (w->cargo_count < w->cargo_cap && v->cargo_type == w->cargo_type) { + has_space_for_same_type = true; + } + + if (w->cargo_count != 0) { + if (v->cargo_type == w->cargo_type && + u->last_station_visited == w->cargo_source) { + other_has_same_type = true; + } + other_has_any_cargo = true; + } + } + + if (has_space_for_same_type) { + if (other_has_same_type) return true; + if (other_has_any_cargo && !has_any_cargo) return true; + } + } + } + + return false; +} + +int LoadUnloadVehicle(Vehicle *v, bool just_arrived) +{ + int profit = 0; + int v_profit = 0; //virtual profit for feeder systems + int v_profit_total = 0; + int unloading_time = 20; + Vehicle *u = v; + int result = 0; + StationID last_visited; + Station *st; + int t; + uint count, cap; + PlayerID old_player; + bool completely_empty = true; + byte load_amount; + bool anything_loaded = false; + + assert(v->current_order.type == OT_LOADING); + + v->cur_speed = 0; + + /* Loading can only have finished when all the cargo has been unloaded, and + * there is nothing left to load. It's easier to clear this if the + * conditions haven't been met than attempting to check them all before + * enabling though. */ + SETBIT(v->load_status, LS_LOADING_FINISHED); + + old_player = _current_player; + _current_player = v->owner; + + last_visited = v->last_station_visited; + st = GetStation(last_visited); + + for (; v != NULL; v = v->next) { + GoodsEntry* ge; + load_amount = EngInfo(v->engine_type)->load_amount; + if (_patches.gradual_loading) { + uint16 cb_load_amount = GetVehicleCallback(CBID_VEHICLE_LOAD_AMOUNT, 0, 0, v->engine_type, v); + if (cb_load_amount != CALLBACK_FAILED) load_amount = cb_load_amount & 0xFF; + } + + if (v->cargo_cap == 0) continue; + + /* If the vehicle has just arrived, set it to unload. */ + if (just_arrived) SETBIT(v->load_status, LS_CARGO_UNLOADING); + + ge = &st->goods[v->cargo_type]; + count = GB(ge->waiting_acceptance, 0, 12); + + /* unload? */ + if (v->cargo_count != 0 && HASBIT(v->load_status, LS_CARGO_UNLOADING)) { + uint16 amount_unloaded = _patches.gradual_loading ? min(v->cargo_count, load_amount) : v->cargo_count; + + CLRBIT(u->load_status, LS_LOADING_FINISHED); + + if (v->cargo_source != last_visited && ge->waiting_acceptance & 0x8000 && !(u->current_order.flags & OF_TRANSFER)) { + // deliver goods to the station + st->time_since_unload = 0; + + unloading_time += v->cargo_count; /* TTDBUG: bug in original TTD */ + if (just_arrived && !HASBIT(v->load_status, LS_CARGO_PAID_FOR)) { + profit += DeliverGoods(v->cargo_count, v->cargo_type, v->cargo_source, last_visited, v->cargo_days); + SETBIT(v->load_status, LS_CARGO_PAID_FOR); + } + result |= 1; + v->cargo_count -= amount_unloaded; + if (_patches.gradual_loading) continue; + } else if (u->current_order.flags & (OF_UNLOAD | OF_TRANSFER)) { + /* unload goods and let it wait at the station */ + st->time_since_unload = 0; + if (just_arrived && (u->current_order.flags & OF_TRANSFER) && !HASBIT(v->load_status, LS_CARGO_PAID_FOR)) { + v_profit = GetTransportedGoodsIncome( + v->cargo_count, + DistanceManhattan(GetStation(v->cargo_source)->xy, GetStation(last_visited)->xy), + v->cargo_days, + v->cargo_type) * 3 / 2; + + v_profit_total += v_profit; + SETBIT(v->load_status, LS_CARGO_PAID_FOR); + } + + unloading_time += v->cargo_count; + t = GB(ge->waiting_acceptance, 0, 12); + if (t == 0) { + // No goods waiting at station + ge->enroute_time = v->cargo_days; + ge->enroute_from = v->cargo_source; + } else { + // Goods already waiting at station. Set counters to the worst value. + if (v->cargo_days >= ge->enroute_time) + ge->enroute_time = v->cargo_days; + if (last_visited != ge->enroute_from) + ge->enroute_from = v->cargo_source; + } + // Update amount of waiting cargo + SB(ge->waiting_acceptance, 0, 12, min(amount_unloaded + t, 0xFFF)); + + if (u->current_order.flags & OF_TRANSFER) { + ge->feeder_profit += v_profit; + u->profit_this_year += v_profit; + } + result |= 2; + v->cargo_count -= amount_unloaded; + if (_patches.gradual_loading) continue; + } + + if (v->cargo_count != 0) completely_empty = false; + } + + /* The vehicle must have been unloaded because it is either empty, or + * the UNLOADING bit is already clear in v->load_status. */ + CLRBIT(v->load_status, LS_CARGO_UNLOADING); + CLRBIT(v->load_status, LS_CARGO_PAID_FOR); + + /* don't pick up goods that we unloaded */ + if (u->current_order.flags & OF_UNLOAD) continue; + + /* update stats */ + ge->days_since_pickup = 0; + switch (u->type) { + case VEH_Train: t = u->u.rail.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 - v->build_year; + + // If there's goods waiting at the station, and the vehicle + // has capacity for it, load it on the vehicle. + if (count != 0 && + (cap = v->cargo_cap - v->cargo_count) != 0) { + int cargoshare; + int feeder_profit_share; + + if (v->cargo_count == 0) + TriggerVehicle(v, VEHICLE_TRIGGER_NEW_CARGO); + + /* Skip loading this vehicle if another train/vehicle is already handling + * the same cargo type at this station */ + if (_patches.improved_load && (u->current_order.flags & OF_FULL_LOAD) && LoadWait(v,u)) continue; + + /* 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_empty assignment can then be safely + * removed; that's how TTDPatch behaves too. --pasky */ + completely_empty = false; + anything_loaded = true; + + if (cap > count) cap = count; + if (_patches.gradual_loading) cap = min(cap, load_amount); + if (cap < count) CLRBIT(u->load_status, LS_LOADING_FINISHED); + cargoshare = cap * 10000 / ge->waiting_acceptance; + feeder_profit_share = ge->feeder_profit * cargoshare / 10000; + v->cargo_count += cap; + ge->waiting_acceptance -= cap; + u->profit_this_year -= feeder_profit_share; + ge->feeder_profit -= feeder_profit_share; + unloading_time += cap; + st->time_since_load = 0; + + // And record the source of the cargo, and the days in travel. + v->cargo_source = ge->enroute_from; + v->cargo_days = ge->enroute_time; + result |= 2; + st->last_vehicle_type = v->type; + } + } + + v = u; + + if (_patches.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 */ + uint gradual_loading_wait_time[] = { 40, 20, 10, 20 }; + + unloading_time = gradual_loading_wait_time[v->type - VEH_Train]; + if (HASBIT(v->load_status, LS_LOADING_FINISHED)) { + if (anything_loaded) { + unloading_time += 20; + } else { + unloading_time = 20; + } + } + } + + if (v_profit_total > 0) { + ShowFeederIncomeAnimation(v->x_pos, v->y_pos, v->z_pos, v_profit_total); + } + + if (v->type == VEH_Train) { + // Each platform tile is worth 2 rail vehicles. + int overhang = v->u.rail.cached_total_length - GetStationPlatforms(st, v->tile) * TILE_SIZE; + if (overhang > 0) { + unloading_time <<= 1; + unloading_time += (overhang * unloading_time) / 8; + } + } + + v->load_unload_time_rem = unloading_time; + + if (completely_empty) { + TriggerVehicle(v, VEHICLE_TRIGGER_EMPTY); + } + + if (result != 0) { + InvalidateWindow(WC_VEHICLE_DETAILS, v->index); + MarkStationTilesDirty(st); + + if (result & 2) InvalidateWindow(WC_STATION_VIEW, last_visited); + + if (profit != 0) { + v->profit_this_year += profit; + SubtractMoneyFromPlayer(-profit); + + if (IsLocalPlayer() && !PlayVehicleSound(v, VSE_LOAD_UNLOAD)) { + SndPlayVehicleFx(SND_14_CASHTILL, v); + } + + ShowCostOrIncomeAnimation(v->x_pos, v->y_pos, v->z_pos, -profit); + } + } + + _current_player = old_player; + return result; +} + +void PlayersMonthlyLoop(void) +{ + PlayersGenStatistics(); + if (_patches.inflation && _cur_year < MAX_YEAR) + AddInflation(); + PlayersPayInterest(); + // Reset the _current_player flag + _current_player = OWNER_NONE; + HandleEconomyFluctuations(); + SubsidyMonthlyHandler(); +} + +static void DoAcquireCompany(Player *p) +{ + Player *owner; + int i,pi; + int64 value; + + SetDParam(0, p->name_1); + SetDParam(1, p->name_2); + SetDParam(2, p->bankrupt_value); + AddNewsItem( (StringID)(_current_player | NB_BMERGER), NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); + + // original code does this a little bit differently + pi = p->index; + ChangeOwnershipOfPlayerItems(pi, _current_player); + + if (p->bankrupt_value == 0) { + owner = GetPlayer(_current_player); + owner->current_loan += p->current_loan; + } + + value = CalculateCompanyValue(p) >> 2; + for (i = 0; i != 4; i++) { + if (p->share_owners[i] != PLAYER_SPECTATOR) { + owner = GetPlayer(p->share_owners[i]); + owner->money64 += value; + owner->yearly_expenses[0][EXPENSES_OTHER] += value; + UpdatePlayerMoney32(owner); + } + } + + p->is_active = false; + + DeletePlayerWindows(pi); + RebuildVehicleLists(); //Updates the open windows to add the newly acquired vehicles to the lists +} + +extern int GetAmountOwnedBy(Player *p, byte owner); + +/** Acquire shares in an opposing company. + * @param tile unused + * @param p1 player to buy the shares from + * @param p2 unused + */ +int32 CmdBuyShareInCompany(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) +{ + Player *p; + int64 cost; + + /* Check if buying shares is allowed (protection against modified clients */ + if (!IsValidPlayer((PlayerID)p1) || !_patches.allow_shares) return CMD_ERROR; + + SET_EXPENSES_TYPE(EXPENSES_OTHER); + p = GetPlayer(p1); + + /* Protect new companies from hostile takeovers */ + if (_cur_year - p->inaugurated_year < 6) return_cmd_error(STR_7080_PROTECTED); + + /* Those lines are here for network-protection (clients can be slow) */ + if (GetAmountOwnedBy(p, PLAYER_SPECTATOR) == 0) return 0; + + /* We can not buy out a real player (temporarily). TODO: well, enable it obviously */ + if (GetAmountOwnedBy(p, PLAYER_SPECTATOR) == 1 && !p->is_ai) return 0; + + cost = CalculateCompanyValue(p) >> 2; + if (flags & DC_EXEC) { + PlayerID* b = p->share_owners; + int i; + + while (*b != PLAYER_SPECTATOR) b++; /* share owners is guaranteed to contain at least one PLAYER_SPECTATOR */ + *b = _current_player; + + for (i = 0; p->share_owners[i] == _current_player;) { + if (++i == 4) { + p->bankrupt_value = 0; + DoAcquireCompany(p); + break; + } + } + InvalidateWindow(WC_COMPANY, p1); + } + return cost; +} + +/** Sell shares in an opposing company. + * @param tile unused + * @param p1 player to sell the shares from + * @param p2 unused + */ +int32 CmdSellShareInCompany(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) +{ + Player *p; + int64 cost; + + /* Check if buying shares is allowed (protection against modified clients */ + if (!IsValidPlayer((PlayerID)p1) || !_patches.allow_shares) return CMD_ERROR; + + SET_EXPENSES_TYPE(EXPENSES_OTHER); + p = GetPlayer(p1); + + /* Those lines are here for network-protection (clients can be slow) */ + if (GetAmountOwnedBy(p, _current_player) == 0) return 0; + + /* adjust it a little to make it less profitable to sell and buy */ + cost = CalculateCompanyValue(p) >> 2; + cost = -(cost - (cost >> 7)); + + if (flags & DC_EXEC) { + PlayerID* b = p->share_owners; + while (*b != _current_player) b++; /* share owners is guaranteed to contain player */ + *b = PLAYER_SPECTATOR; + InvalidateWindow(WC_COMPANY, p1); + } + return 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 players + * @param tile unused + * @param p1 player/company to buy up + * @param p2 unused + */ +int32 CmdBuyCompany(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) +{ + Player *p; + + /* Disable takeovers in multiplayer games */ + if (!IsValidPlayer((PlayerID)p1) || _networking) return CMD_ERROR; + + SET_EXPENSES_TYPE(EXPENSES_OTHER); + p = GetPlayer(p1); + + if (!p->is_ai) return CMD_ERROR; + + if (flags & DC_EXEC) { + DoAcquireCompany(p); + } + return p->bankrupt_value; +} + +// Prices +static void SaveLoad_PRIC(void) +{ + SlArray(&_price, NUM_PRICES, SLE_INT32); + SlArray(&_price_frac, NUM_PRICES, SLE_UINT16); +} + +// Cargo payment rates +static void SaveLoad_CAPR(void) +{ + SlArray(&_cargo_payment_rates, NUM_CARGO, SLE_INT32); + SlArray(&_cargo_payment_rates_frac, NUM_CARGO, SLE_UINT16); +} + +static const SaveLoad _economy_desc[] = { + SLE_VAR(Economy, max_loan, SLE_INT32), + SLE_VAR(Economy, max_loan_unround, SLE_INT32), + SLE_VAR(Economy, fluct, SLE_FILE_I16 | SLE_VAR_I32), + SLE_VAR(Economy, interest_rate, SLE_UINT8), + SLE_VAR(Economy, infl_amount, SLE_UINT8), + SLE_VAR(Economy, infl_amount_pr, SLE_UINT8), + SLE_END() +}; + +// Economy variables +static void SaveLoad_ECMY(void) +{ + SlObject(&_economy, _economy_desc); +} + +const ChunkHandler _economy_chunk_handlers[] = { + { 'PRIC', SaveLoad_PRIC, SaveLoad_PRIC, CH_RIFF | CH_AUTO_LENGTH}, + { 'CAPR', SaveLoad_CAPR, SaveLoad_CAPR, CH_RIFF | CH_AUTO_LENGTH}, + { 'SUBS', Save_SUBS, Load_SUBS, CH_ARRAY}, + { 'ECMY', SaveLoad_ECMY, SaveLoad_ECMY, CH_RIFF | CH_LAST}, +}; |