#include "stdafx.h" #include "openttd.h" #include "strings.h" // XXX InjectDParam() #include "table/strings.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.h" #include "sound.h" #include "engine.h" #include "network_data.h" // get a mask of the allowed currencies depending on the year uint GetMaskOfAllowedCurrencies(void) { int i; uint mask = 0; for (i = 0; i != lengthof(_currency_specs); i++) { uint16 to_euro = _currency_specs[i].to_euro; if (i == 23) mask |= (1 << 23); // always allow custom currency if (to_euro != CF_NOEURO && to_euro != CF_ISEURO && _cur_year >= (to_euro-MAX_YEAR_BEGIN_REAL)) continue; if (_cur_year < (2000-MAX_YEAR_BEGIN_REAL) && (to_euro == CF_ISEURO)) continue; mask |= (1 << i); } return mask; } void CheckSwitchToEuro(void) { if (_currency_specs[_opt.currency].to_euro != CF_NOEURO && _currency_specs[_opt.currency].to_euro != CF_ISEURO && _cur_year >= (_currency_specs[_opt.currency].to_euro-MAX_YEAR_BEGIN_REAL)) { _opt.currency = 2; // this is the index of euro above. AddNewsItem(STR_EURO_INTRODUCE, NEWS_FLAGS(NM_NORMAL,0,NT_ECONOMY,0), 0, 0); } } void UpdatePlayerHouse(Player *p, uint score) { byte val; uint tile = p->location_of_house; if (tile == 0) return; (val = 128, score < 170) || (val+= 4, score < 350) || (val+= 4, score < 520) || (val+= 4, score < 720) || (val+= 4, true); /* house is already big enough */ if (val <= _map5[tile]) return; _map5[tile + TILE_XY(0,0)] = val; _map5[tile + TILE_XY(0,1)] = ++val; _map5[tile + TILE_XY(1,0)] = ++val; _map5[tile + TILE_XY(1,1)] = ++val; MarkTileDirtyByTile(tile + TILE_XY(0,0)); MarkTileDirtyByTile(tile + TILE_XY(0,1)); MarkTileDirtyByTile(tile + TILE_XY(1,0)); MarkTileDirtyByTile(tile + TILE_XY(1,1)); } int64 CalculateCompanyValue(Player *p) { byte owner = p->index; int64 value; { Station *st; uint num = 0; FOR_ALL_STATIONS(st) { if (st->xy != 0 && 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 = score_info[SCORE_MIN_PROFIT].needed; uint num = 0; FOR_ALL_VEHICLES(v) { if (v->owner != owner) continue; if ((v->type == VEH_Train && v->subtype == TS_Front_Engine) || v->type == VEH_Road || (v->type == VEH_Aircraft && v->subtype<=2) || v->type == VEH_Ship) { num++; if (v->age > 730) { if (min_profit > v->profit_last_year) min_profit = v->profit_last_year; } } } _score_part[owner][SCORE_VEHICLES] = num; if (min_profit > 0) _score_part[owner][SCORE_MIN_PROFIT] = min_profit; } /* Count stations */ { uint num = 0; Station *st; FOR_ALL_STATIONS(st) { if (st->xy != 0 && 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 */ { 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 */ { 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; UpdatePlayerHouse(p, score); p->old_economy[0].company_value = CalculateCompanyValue(p); } InvalidateWindow(WC_PERFORMANCE_DETAIL, 0); return score; } // use 255 as new_player to delete the player. void ChangeOwnershipOfPlayerItems(PlayerID old_player, PlayerID new_player) { PlayerID old = _current_player; _current_player = old_player; if (new_player == OWNER_SPECTATOR) { Subsidy *s; for (s = _subsidies; s != endof(_subsidies); s++) { if (s->cargo_type != 0xff && s->age >= 12) { if (GetStation(s->to)->owner == old_player) s->cargo_type = 0xff; } } } /* Take care of rating in towns */ { Town *t; if (new_player != OWNER_SPECTATOR) { FOR_ALL_TOWNS(t) { /* If a player takes over, give the ratings to that player. */ if (IsValidTown(t) && 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 ratings for the town */ if (IsValidTown(t)) { 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 (v->subtype == TS_Front_Engine) 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 == OWNER_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 (v->type == VEH_Train && v->subtype == TS_Front_Engine) v->unitnumber = ++num_train; else if (v->type == VEH_Road) v->unitnumber = ++num_road; else if (v->type == VEH_Ship) v->unitnumber = ++num_ship; else if (v->type == VEH_Aircraft && v->subtype <= 2) v->unitnumber = ++num_aircraft; } } } } // 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 != OWNER_SPECTATOR) { Window *w; for (w = _windows; w != _last_window; w++) { if (w->caption_color == old_player) w->caption_color = 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] = 0xFF; } } p = DEREF_PLAYER(_current_player); /* Sell all the shares that people have on this company */ for (i = 0; i < 4; i++) p->share_owners[i] = 0xFF; } _current_player = old; MarkWholeScreenDirty(); } extern void DeletePlayerWindows(int pi); static void PlayersCheckBankrupt(Player *p) { int 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 + 16), 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 (IS_HUMAN_PLAYER(owner)) { AddNewsItem( (StringID)(owner + 16), 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 + 16*3), NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); // If the player is human, and it is no network play, leave the player playing if (IS_HUMAN_PLAYER(owner) && !_networking) { p->bankrupt_asked = 255; p->bankrupt_timeout = 0x456; } else { #ifdef ENABLE_NETWORK if (IS_HUMAN_PLAYER(owner) && _network_server) { // If we are the server, make sure it is clear that his player is no // longer with us! NetworkClientInfo *ci; NetworkClientState *cs; /* Find all clients that were in control of this company */ FOR_ALL_CLIENTS(cs) { ci = DEREF_CLIENT_INFO(cs); if ((ci->client_playas-1) == owner) { ci->client_playas = OWNER_SPECTATOR; // Send the new info to all the clients NetworkUpdateClientInfo(_network_own_client_index); } } } // Make sure the player no longer controls the company if (IS_HUMAN_PLAYER(owner) && owner == _local_player) { // Switch the player to spectator.. _local_player = OWNER_SPECTATOR; } #endif /* ENABLE_NETWORK */ // Convert everything the player owns to NO_OWNER p->money64 = p->player_money = 100000000; ChangeOwnershipOfPlayerItems(owner, 0xFF); // 255 is no owner // Register the player as not-active p->is_active = false; } } } } void DrawNewsBankrupcy(Window *w) { Player *p; DrawNewsBorder(w); p = DEREF_PLAYER(WP(w,news_d).ni->string_id & 15); DrawPlayerFace(p->face, p->player_color, 2, 23); GfxFillRect(3, 23, 3+91, 23+118, 0x4323); 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 >> 4) { case 1: 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 2: { 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 3: 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 4: 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(NewsItem *ni) { Player *p = DEREF_PLAYER(ni->string_id & 0xF); switch(ni->string_id >> 4) { case 1: 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 2: 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 3: 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 4: 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) { if (st->xy != 0) { _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, &p->cur_economy, sizeof(p->old_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) { 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)(Random()&3); AddNewsItem(STR_7073_WORLD_RECESSION_FINANCIAL, NEWS_FLAGS(NM_NORMAL,0,NT_ECONOMY,0), 0, 0); } else if (_economy.fluct == -12) { _economy.fluct = (Random()&255) + 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 }; 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; } } ((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 = (byte)(Random()) + 168; } Pair SetupSubsidyDecodeParam(Subsidy *s, bool mode) { int tile, tile2; Industry *i; Town *t; Station *st; Pair tp; /* if mode is false, convert into plural */ 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_2029); i = GetIndustry(s->from); tile = i->xy; SetDParam(2, i->town->townnametype); SetDParam(3, i->town->townnameparts); SetDParam(4, i->type + STR_4802_COAL_MINE); if (s->cargo_type != CT_GOODS && s->cargo_type != CT_FOOD) { SetDParam(5, STR_2029); i = GetIndustry(s->to); tile2 = i->xy; SetDParam(8, i->type + STR_4802_COAL_MINE); SetDParam(6, i->town->townnametype); SetDParam(7, i->town->townnameparts); } else { t = GetTown(s->to); tile2 = t->xy; SetDParam(5, t->townnametype); SetDParam(6, t->townnameparts); } } else { t = GetTown(s->from); tile = t->xy; SetDParam(1, t->townnametype); SetDParam(2, t->townnameparts); t = GetTown(s->to); tile2 = t->xy; SetDParam(3, t->townnametype); SetDParam(4, t->townnameparts); } } else { st = GetStation(s->from); tile = st->xy; SetDParam(1, st->index); st = GetStation(s->to); tile2 = st->xy; SetDParam(2, st->index); } tp.a = tile; tp.b = tile2; return tp; } void DeleteSubsidyWithIndustry(uint16 index) { Subsidy *s; for(s=_subsidies; s != endof(_subsidies); s++) { if (s->cargo_type != 0xFF && 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 = 0xFF; } } } void DeleteSubsidyWithStation(uint16 index) { Subsidy *s; bool dirty = false; for(s=_subsidies; s != endof(_subsidies); s++) { if (s->cargo_type != 0xFF && s->age >= 12 && (s->from == index || s->to == index)) { s->cargo_type = 0xFF; dirty = true; } } if (dirty) InvalidateWindow(WC_SUBSIDIES_LIST, 0); } typedef struct FoundRoute { uint distance; byte cargo; void *from; void *to; } FoundRoute; static void FindSubsidyPassengerRoute(FoundRoute *fr) { Town *from,*to; fr->distance = (uint)-1; fr->from = from = GetTown(RandomRange(_total_towns)); if (from->xy == 0 || from->population < 400) return; fr->to = to = GetTown(RandomRange(_total_towns)); if (from==to || to->xy == 0 || 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; byte cargo; fr->distance = (uint)-1; fr->from = i = GetIndustry(RandomRange(_total_industries)); if (i->xy == 0) return; // Randomize cargo type if (Random()&1 && i->produced_cargo[1] != 0xFF) { 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 == 0xFF || cargo == CT_PASSENGERS) return; fr->cargo = cargo; if (cargo == CT_GOODS || cargo == CT_FOOD) { // The destination is a town Town *t = GetTown(RandomRange(_total_towns)); // Only want big towns if (t->xy == 0 || t->population < 900) return; fr->distance = DistanceManhattan(i->xy, t->xy); fr->to = t; } else { // The destination is an industry Industry *i2 = GetIndustry(RandomRange(_total_industries)); // The industry must accept the cargo if (i == i2 || i2->xy == 0 || (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) { Subsidy *ss; for(ss=_subsidies; ss != endof(_subsidies); ss++) { if (s != ss && // ss->age < 12 && ss->from == s->from && ss->to == s->to && ss->cargo_type == s->cargo_type) { s->cargo_type = 0xFF; return true; } } return false; } void RemoteSubsidyAdd(Subsidy *s_new) { Subsidy *s; Pair pair; // search the first free subsidy for(s=_subsidies; s != endof(_subsidies); s++) if (s->cargo_type == 0xFF) break; memcpy(s,s_new,sizeof(Subsidy)); pair = SetupSubsidyDecodeParam(s, 0); AddNewsItem(STR_2030_SERVICE_SUBSIDY_OFFERED, NEWS_FLAGS(NM_NORMAL, NF_TILE, NT_SUBSIDIES, 0), pair.a, pair.b); InvalidateWindow(WC_SUBSIDIES_LIST, 0); } 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 == 0xFF) 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 = 0xFF; 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 = 0xFF; modified = true; } else { s->age++; } } // 25% chance to go on if (CHANCE16(1,4)) { // Find a free slot s = _subsidies; while (s->cargo_type != 0xFF) { 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, 255), SLE_CONDVAR(Subsidy,to, SLE_FILE_U8 | SLE_VAR_U16, 0, 4), SLE_CONDVAR(Subsidy,to, SLE_UINT16, 5, 255), 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 != 0xFF) { 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, byte cargo_type) { int 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, byte cargo_type, int num_pieces) { Industry *ind, *best; int t, u; /* Check if there's an industry close to the station that accepts * the cargo */ best = NULL; u = _patches.station_spread + 8; FOR_ALL_INDUSTRIES(ind) { if (ind->xy != 0 && (cargo_type == ind->accepts_cargo[0] || cargo_type == ind->accepts_cargo[1] || cargo_type == ind->accepts_cargo[2]) && ind->produced_cargo[0] != 0xFF && ind->produced_cargo[0] != cargo_type && (t = DistanceManhattan(ind->xy, xy)) < 2 * 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, byte 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 */ if (cargo_type == CT_PASSENGERS || cargo_type == CT_MAIL || cargo_type == CT_GOODS || cargo_type == CT_FOOD) { xy = GetTown(s->to)->xy; } else { xy = (GetIndustry(s->to))->xy; } 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 = DEREF_PLAYER(_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, byte cargo_type, uint16 source, uint16 dest, byte days_in_transit) { bool subsidised; Station *s_from, *s_to; int32 profit; assert(num_pieces > 0); // Update player statistics { Player *p = DEREF_PLAYER(_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 { Town *t = s_to->town; if (cargo_type == CT_FOOD) t->new_act_food += num_pieces; if (cargo_type == CT_WATER) t->new_act_water += num_pieces; } // Give the goods to the industry. DeliverGoodsToIndustry(s_to->xy, cargo_type, num_pieces); // Determine profit { int t = DistanceManhattan(s_from->xy, s_to->xy); int r = num_pieces; profit = 0; do { int u = min(r, 255); r -= u; profit += GetTransportedGoodsIncome(u, t, days_in_transit, cargo_type); } while (r != 0); } // Modify profit if a subsidy is in effect if (subsidised) { if (_opt.diff.subsidy_multiplier < 1) { /* 1.5x */ profit += profit >> 1; } else if (_opt.diff.subsidy_multiplier == 1) { /* 2x */ profit *= 2; } else if (_opt.diff.subsidy_multiplier == 2) { /* 3x */ profit *= 3; } else { /* 4x */ profit *= 4; } } // Computers get 25% extra profit if they're intelligent. if (_opt.diff.competitor_intelligence>=1 && !IS_HUMAN_PLAYER(_current_player)) profit += profit >> 2; 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 || x->subtype == TS_Front_Engine) && // for all locs u->last_station_visited == x->last_station_visited && // at the same station !(x->vehstatus & VS_STOPPED) && // not stopped 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) { int profit = 0; int unloading_time = 20; Vehicle *u = v; int result = 0; uint16 last_visited; Station *st; GoodsEntry *ge; int t; uint count, cap; byte old_player; bool completely_empty = true; assert(v->current_order.type == OT_LOADING); v->cur_speed = 0; old_player = _current_player; _current_player = v->owner; st = GetStation(last_visited = v->last_station_visited); for (; v != NULL; v = v->next) { if (v->cargo_cap == 0) continue; ge = &st->goods[v->cargo_type]; /* unload? */ if (v->cargo_count != 0) { if (v->cargo_source != last_visited && ge->waiting_acceptance & 0x8000) { // deliver goods to the station st->time_since_unload = 0; unloading_time += v->cargo_count; /* TTDBUG: bug in original TTD */ profit += DeliverGoods(v->cargo_count, v->cargo_type, v->cargo_source, last_visited, v->cargo_days); result |= 1; v->cargo_count = 0; } else if (u->current_order.flags & OF_UNLOAD) { /* unload goods and let it wait at the station */ st->time_since_unload = 0; if ((t=ge->waiting_acceptance & 0xFFF) == 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 ge->waiting_acceptance = (ge->waiting_acceptance &~0xFFF) | min(v->cargo_count + t, 0xFFF); result |= 2; v->cargo_count = 0; } if (v->cargo_count != 0) completely_empty = false; } /* don't pick up goods that we unloaded */ if (u->current_order.flags & OF_UNLOAD) continue; /* update stats */ ge->days_since_pickup = 0; t = u->max_speed; if (u->type == VEH_Road) t >>=1; if (u->type == VEH_Train) t = u->u.rail.cached_max_speed; // if last speed is 0, we treat that as if no vehicle has ever visited the station. ge->last_speed = t < 255 ? 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=ge->waiting_acceptance & 0xFFF) != 0 && (cap = v->cargo_cap - v->cargo_count) != 0) { 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 && 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; if (cap > count) cap = count; v->cargo_count += cap; ge->waiting_acceptance -= cap; 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 = v->index; } } v = u; if (v->type == VEH_Train) { int num = - (int)GetStationPlatforms(st, v->tile) * 2; do num++; while ( (v=v->next) != NULL); if (num > 0) { unloading_time <<=1; unloading_time += num * unloading_time; } v = u; } v->load_unload_time_rem = unloading_time; if (completely_empty) { TriggerVehicle(v, VEHICLE_TRIGGER_EMPTY); } if (result != 0) { InvalidateWindow(WC_VEHICLE_DETAILS, v->index); if (result & 2) InvalidateWindow(WC_STATION_VIEW, last_visited); if (profit != 0) { v->profit_this_year += profit; SubtractMoneyFromPlayer(-profit); if (_current_player == _local_player) 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_END) 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 + 16*2), 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 = DEREF_PLAYER(_current_player); owner->current_loan += p->current_loan; } value = CalculateCompanyValue(p) >> 2; for(i=0; i!=4; i++) { if (p->share_owners[i] != 0xFF) { owner = DEREF_PLAYER(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 x,y unused * @param p1 player to buy the shares from * @param p2 unused */ int32 CmdBuyShareInCompany(int x, int y, uint32 flags, uint32 p1, uint32 p2) { Player *p; int64 cost; /* Check if buying shares is allowed (protection against modified clients */ if (p1 >= MAX_PLAYERS || !_patches.allow_shares) return CMD_ERROR; SET_EXPENSES_TYPE(EXPENSES_OTHER); p = DEREF_PLAYER(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, OWNER_SPECTATOR) == 0) return 0; /* We can not buy out a real player (temporarily). TODO: well, enable it obviously */ if (GetAmountOwnedBy(p, OWNER_SPECTATOR) == 1 && !p->is_ai) return 0; cost = CalculateCompanyValue(p) >> 2; if (flags & DC_EXEC) { int i; byte *b = p->share_owners; while (*b != 0xFF) b++; /* share owners is guaranteed to contain at least one 0xFF */ *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, (int)p1); } return cost; } /** Sell shares in an opposing company. * @param x,y unused * @param p1 player to sell the shares from * @param p2 unused */ int32 CmdSellShareInCompany(int x, int y, uint32 flags, uint32 p1, uint32 p2) { Player *p; int64 cost; /* Check if buying shares is allowed (protection against modified clients */ if (p1 >= MAX_PLAYERS || !_patches.allow_shares) return CMD_ERROR; SET_EXPENSES_TYPE(EXPENSES_OTHER); p = DEREF_PLAYER(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) { byte *b = p->share_owners; while (*b != _current_player) b++; /* share owners is guaranteed to contain player */ *b = 0xFF; InvalidateWindow(WC_COMPANY, (int)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 x,y unused * @param p1 player/company to buy up * @param p2 unused */ int32 CmdBuyCompany(int x, int y, uint32 flags, uint32 p1, uint32 p2) { Player *p; /* Disable takeovers in multiplayer games */ if (p1 >= MAX_PLAYERS || _networking) return CMD_ERROR; SET_EXPENSES_TYPE(EXPENSES_OTHER); p = DEREF_PLAYER(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}, };