tron@2186: /* $Id$ */ tron@2186: belugas@6451: /** @file economy.cpp */ belugas@6451: truelight@0: #include "stdafx.h" Darkvater@1891: #include "openttd.h" tron@2291: #include "currency.h" maedhros@6949: #include "landscape.h" truelight@0: #include "news.h" rubidium@8750: #include "player_base.h" rubidium@8750: #include "player_func.h" truelight@0: #include "station.h" rubidium@8612: #include "command_func.h" truelight@0: #include "saveload.h" truelight@0: #include "industry.h" truelight@0: #include "town.h" rubidium@5720: #include "network/network.h" tron@445: #include "engine.h" rubidium@5720: #include "network/network_data.h" tron@2159: #include "variables.h" tron@2159: #include "vehicle_gui.h" truelight@2395: #include "ai/ai.h" bjarni@2676: #include "train.h" maedhros@7353: #include "roadveh.h" Darkvater@6105: #include "aircraft.h" peter1138@2962: #include "newgrf_engine.h" peter1138@4701: #include "newgrf_sound.h" peter1138@5211: #include "newgrf_callbacks.h" rubidium@7664: #include "newgrf_industries.h" rubidium@7725: #include "newgrf_industrytiles.h" celestar@3386: #include "unmovable.h" peter1138@6417: #include "cargotype.h" rubidium@6516: #include "player_face.h" rubidium@7139: #include "group.h" rubidium@8610: #include "strings_func.h" rubidium@8615: #include "tile_cmd.h" rubidium@8627: #include "functions.h" rubidium@8627: #include "window_func.h" rubidium@8636: #include "date_func.h" rubidium@8640: #include "vehicle_func.h" rubidium@8653: #include "sound_func.h" smatz@8696: #include "track_type.h" smatz@8696: #include "track_func.h" smatz@8838: #include "road_func.h" smatz@8696: #include "rail_map.h" smatz@8734: #include "signal_func.h" rubidium@8720: #include "gfx_func.h" rubidium@10366: #include "autoreplace_func.h" truelight@0: rubidium@8760: #include "table/strings.h" rubidium@8760: #include "table/sprites.h" rubidium@8760: skidd13@8422: /** skidd13@8422: * Multiply two integer values and shift the results to right. skidd13@8422: * skidd13@8422: * This function multiplies two integer values. The result is skidd13@8422: * shifted by the amount of shift to right. skidd13@8422: * skidd13@8422: * @param a The first integer skidd13@8422: * @param b The second integer skidd13@8422: * @param shift The amount to shift the value to right. skidd13@8422: * @return The shifted result skidd13@8422: */ skidd13@8422: static inline int32 BigMulS(const int32 a, const int32 b, const uint8 shift) skidd13@8422: { skidd13@8422: return (int32)((int64)a * (int64)b >> shift); skidd13@8422: } skidd13@8422: skidd13@8422: /** skidd13@8422: * Multiply two unsigned integers and shift the results to right. skidd13@8422: * skidd13@8422: * This function multiplies two unsigned integers. The result is skidd13@8422: * shifted by the amount of shift to right. skidd13@8422: * skidd13@8422: * @param a The first unsigned integer skidd13@8422: * @param b The second unsigned integer skidd13@8422: * @param shift The amount to shift the value to right. skidd13@8422: * @return The shifted result skidd13@8422: */ skidd13@8422: static inline uint32 BigMulSU(const uint32 a, const uint32 b, const uint8 shift) skidd13@8422: { skidd13@8422: return (uint32)((uint64)a * (uint64)b >> shift); skidd13@8422: } skidd13@8422: belugas@6451: /* Score info */ ludde@2261: const ScoreInfo _score_info[] = { tron@4077: { SCORE_VEHICLES, 120, 100 }, tron@4077: { SCORE_STATIONS, 80, 100 }, tron@4077: { SCORE_MIN_PROFIT, 10000, 100 }, tron@4077: { SCORE_MIN_INCOME, 50000, 50 }, tron@4077: { SCORE_MAX_INCOME, 100000, 100 }, tron@4077: { SCORE_DELIVERED, 40000, 400 }, tron@4077: { SCORE_CARGO, 8, 50 }, tron@4077: { SCORE_MONEY, 10000000, 50 }, tron@4077: { SCORE_LOAN, 250000, 50 }, tron@4077: { SCORE_TOTAL, 0, 0 } ludde@2261: }; ludde@2261: rubidium@5838: int _score_part[MAX_PLAYERS][SCORE_END]; rubidium@8612: Economy _economy; rubidium@8612: Subsidy _subsidies[MAX_PLAYERS]; rubidium@8612: Prices _price; rubidium@8612: uint16 _price_frac[NUM_PRICES]; rubidium@8615: Money _cargo_payment_rates[NUM_CARGO]; rubidium@8615: uint16 _cargo_payment_rates_frac[NUM_CARGO]; rubidium@8615: Money _additional_cash_required; ludde@2261: rubidium@7449: Money CalculateCompanyValue(const Player* p) tron@2475: { tron@2475: PlayerID owner = p->index; rubidium@8277: Money value = 0; truelight@193: rubidium@8277: Station *st; rubidium@8277: uint num = 0; truelight@193: rubidium@8277: FOR_ALL_STATIONS(st) { truelight@8328: if (st->owner == owner) num += CountBits(st->facilities); truelight@0: } truelight@0: rubidium@8277: value += num * _price.station_value * 25; truelight@4346: rubidium@8277: Vehicle *v; rubidium@8277: FOR_ALL_VEHICLES(v) { rubidium@8277: if (v->owner != owner) continue; rubidium@8277: rubidium@8277: if (v->type == VEH_TRAIN || rubidium@8277: v->type == VEH_ROAD || rubidium@8277: (v->type == VEH_AIRCRAFT && IsNormalAircraft(v)) || rubidium@8277: v->type == VEH_SHIP) { rubidium@8277: value += v->value * 3 >> 1; truelight@0: } truelight@0: } truelight@0: rubidium@7449: /* Add real money value */ rubidium@8277: value -= p->current_loan; rubidium@8277: value += p->player_money; tron@1019: rubidium@8277: return max(value, (Money)1); truelight@0: } truelight@0: belugas@6451: /** if update is set to true, the economy is updated with this score belugas@6451: * (also the house is updated, should only be true in the on-tick event) belugas@6451: * @param update the economy with calculated score belugas@6451: * @param p player been evaluated belugas@6451: * @return actual score of this player belugas@6451: * */ darkvater@147: int UpdateCompanyRatingAndValue(Player *p, bool update) truelight@0: { truelight@0: byte owner = p->index; dominik@116: int score = 0; dominik@116: dominik@116: memset(_score_part[owner], 0, sizeof(_score_part[owner])); truelight@0: truelight@0: /* Count vehicles */ truelight@0: { truelight@0: Vehicle *v; rubidium@7486: Money min_profit = 0; truelight@2829: bool min_profit_first = true; truelight@0: uint num = 0; truelight@0: truelight@0: FOR_ALL_VEHICLES(v) { tron@4077: if (v->owner != owner) continue; maedhros@7269: if (IsPlayerBuildableVehicleType(v->type) && v->IsPrimaryVehicle()) { truelight@0: num++; truelight@0: if (v->age > 730) { truelight@2829: /* Find the vehicle with the lowest amount of profit */ smatz@9110: if (min_profit_first || min_profit > v->profit_last_year) { smatz@9110: min_profit = v->profit_last_year; truelight@2829: min_profit_first = false; tron@4077: } truelight@0: } truelight@0: } truelight@0: } truelight@0: smatz@9110: min_profit >>= 8; // remove the fract part smatz@9110: dominik@116: _score_part[owner][SCORE_VEHICLES] = num; truelight@2829: /* Don't allow negative min_profit to show */ tron@1407: if (min_profit > 0) rubidium@7486: _score_part[owner][SCORE_MIN_PROFIT] = ClampToI32(min_profit); truelight@0: } truelight@0: truelight@0: /* Count stations */ truelight@0: { truelight@0: uint num = 0; tron@3033: const Station* st; truelight@0: truelight@0: FOR_ALL_STATIONS(st) { truelight@8328: if (st->owner == owner) num += CountBits(st->facilities); truelight@0: } dominik@116: _score_part[owner][SCORE_STATIONS] = num; truelight@0: } truelight@0: truelight@0: /* Generate statistics depending on recent income statistics */ truelight@0: { rubidium@7449: int numec = min(p->num_valid_stat_ent, 12); rubidium@7449: if (numec != 0) { rubidium@7449: const PlayerEconomyEntry *pee = p->old_economy; rubidium@7449: Money min_income = pee->income + pee->expenses; rubidium@7449: Money max_income = pee->income + pee->expenses; truelight@0: truelight@0: do { truelight@0: min_income = min(min_income, pee->income + pee->expenses); truelight@0: max_income = max(max_income, pee->income + pee->expenses); truelight@0: } while (++pee,--numec); truelight@0: truelight@0: if (min_income > 0) rubidium@7486: _score_part[owner][SCORE_MIN_INCOME] = ClampToI32(min_income); truelight@0: rubidium@7486: _score_part[owner][SCORE_MAX_INCOME] = ClampToI32(max_income); truelight@0: } truelight@0: } truelight@0: truelight@0: /* Generate score depending on amount of transported cargo */ truelight@0: { tron@3033: const PlayerEconomyEntry* pee; truelight@0: int numec; truelight@0: uint32 total_delivered; truelight@0: truelight@0: numec = min(p->num_valid_stat_ent, 4); truelight@0: if (numec != 0) { truelight@0: pee = p->old_economy; truelight@0: total_delivered = 0; truelight@0: do { truelight@0: total_delivered += pee->delivered_cargo; truelight@0: } while (++pee,--numec); truelight@0: dominik@116: _score_part[owner][SCORE_DELIVERED] = total_delivered; truelight@0: } truelight@0: } dominik@116: truelight@0: /* Generate score for variety of cargo */ truelight@0: { truelight@8328: uint num = CountBits(p->cargo_types); dominik@116: _score_part[owner][SCORE_CARGO] = num; tron@3033: if (update) p->cargo_types = 0; truelight@0: } truelight@0: truelight@0: /* Generate score for player money */ truelight@0: { rubidium@7486: if (p->player_money > 0) { rubidium@7486: _score_part[owner][SCORE_MONEY] = ClampToI32(p->player_money); truelight@0: } truelight@0: } truelight@0: truelight@0: /* Generate score for loan */ truelight@0: { rubidium@7486: _score_part[owner][SCORE_LOAN] = ClampToI32(_score_info[SCORE_LOAN].needed - p->current_loan); dominik@116: } truelight@193: belugas@6451: /* Now we calculate the score for each item.. */ dominik@116: { dominik@116: int total_score = 0; dominik@116: int s; dominik@116: score = 0; rubidium@5838: for (ScoreID i = SCORE_BEGIN; i < SCORE_END; i++) { belugas@6451: /* Skip the total */ dominik@116: if (i == SCORE_TOTAL) continue; belugas@6451: /* Check the score */ skidd13@8418: s = Clamp(_score_part[owner][i], 0, _score_info[i].needed) * _score_info[i].score / _score_info[i].needed; dominik@116: score += s; ludde@2261: total_score += _score_info[i].score; dominik@116: } truelight@193: dominik@116: _score_part[owner][SCORE_TOTAL] = score; truelight@193: belugas@6451: /* We always want the score scaled to SCORE_MAX (1000) */ tron@3033: if (total_score != SCORE_MAX) score = score * SCORE_MAX / total_score; truelight@0: } truelight@0: dominik@116: if (update) { tron@4077: p->old_economy[0].performance_history = score; tron@4077: UpdateCompanyHQ(p, score); tron@4077: p->old_economy[0].company_value = CalculateCompanyValue(p); tron@4077: } truelight@193: dominik@116: InvalidateWindow(WC_PERFORMANCE_DETAIL, 0); darkvater@147: return score; truelight@0: } truelight@0: belugas@6451: /* use PLAYER_SPECTATOR as new_player to delete the player. */ Darkvater@1797: void ChangeOwnershipOfPlayerItems(PlayerID old_player, PlayerID new_player) truelight@0: { peter1138@5284: Town *t; Darkvater@1797: PlayerID old = _current_player; truelight@6899: celestar@6901: assert(old_player != new_player); celestar@6901: truelight@6899: { truelight@6899: Player *p; truelight@6899: uint i; truelight@6899: truelight@6899: /* See if the old_player had shares in other companies */ truelight@6899: _current_player = old_player; truelight@6899: FOR_ALL_PLAYERS(p) { smatz@8651: if (!p->is_active) continue; truelight@6899: for (i = 0; i < 4; i++) { truelight@6899: if (p->share_owners[i] == old_player) { truelight@6899: /* Sell his shares */ rubidium@7439: CommandCost res = DoCommand(0, p->index, 0, DC_EXEC, CMD_SELL_SHARE_IN_COMPANY); truelight@6899: /* Because we are in a DoCommand, we can't just execute an other one and truelight@6899: * expect the money to be removed. We need to do it ourself! */ truelight@6899: SubtractMoneyFromPlayer(res); truelight@6899: } truelight@6899: } truelight@6899: } truelight@6899: truelight@6899: /* Sell all the shares that people have on this company */ truelight@6899: p = GetPlayer(old_player); truelight@6899: for (i = 0; i < 4; i++) { truelight@6899: _current_player = p->share_owners[i]; truelight@6899: if (_current_player != PLAYER_SPECTATOR) { truelight@6899: /* Sell the shares */ rubidium@7439: CommandCost res = DoCommand(0, old_player, 0, DC_EXEC, CMD_SELL_SHARE_IN_COMPANY); truelight@6899: /* Because we are in a DoCommand, we can't just execute an other one and truelight@6899: * expect the money to be removed. We need to do it ourself! */ truelight@6899: SubtractMoneyFromPlayer(res); truelight@6899: } truelight@6899: } truelight@6899: } truelight@6899: truelight@0: _current_player = old_player; truelight@0: Darkvater@4260: /* Temporarily increase the player's money, to be sure that rubidium@4549: * removing his/her property doesn't fail because of lack of money. rubidium@4549: * Not too drastically though, because it could overflow */ Darkvater@4848: if (new_player == PLAYER_SPECTATOR) { rubidium@7448: GetPlayer(old_player)->player_money = MAX_UVALUE(uint64) >> 2; // jackpot ;p Darkvater@4260: } Darkvater@4260: Darkvater@4848: if (new_player == PLAYER_SPECTATOR) { truelight@0: Subsidy *s; truelight@193: Darkvater@1797: for (s = _subsidies; s != endof(_subsidies); s++) { tron@2469: if (s->cargo_type != CT_INVALID && s->age >= 12) { tron@4077: if (GetStation(s->to)->owner == old_player) s->cargo_type = CT_INVALID; truelight@0: } truelight@0: } truelight@0: } truelight@0: Darkvater@1797: /* Take care of rating in towns */ peter1138@5284: FOR_ALL_TOWNS(t) { peter1138@5284: /* If a player takes over, give the ratings to that player. */ peter1138@5284: if (new_player != PLAYER_SPECTATOR) { skidd13@8424: if (HasBit(t->have_ratings, old_player)) { skidd13@8424: if (HasBit(t->have_ratings, new_player)) { Darkvater@4260: // use max of the two ratings. Darkvater@4260: t->ratings[new_player] = max(t->ratings[new_player], t->ratings[old_player]); Darkvater@4260: } else { skidd13@8427: SetBit(t->have_ratings, new_player); Darkvater@4260: t->ratings[new_player] = t->ratings[old_player]; Darkvater@1797: } Darkvater@4260: } peter1138@5284: } truelight@193: peter1138@5284: /* Reset the ratings for the old player */ peter1138@5284: t->ratings[old_player] = 500; skidd13@8425: ClrBit(t->have_ratings, old_player); truelight@0: } truelight@193: truelight@0: { truelight@0: int num_train = 0; truelight@0: int num_road = 0; truelight@0: int num_ship = 0; truelight@0: int num_aircraft = 0; truelight@0: Vehicle *v; truelight@0: belugas@6451: /* Determine Ids for the new vehicles */ truelight@0: FOR_ALL_VEHICLES(v) { truelight@0: if (v->owner == new_player) { Darkvater@1797: switch (v->type) { rubidium@6585: case VEH_TRAIN: if (IsFrontEngine(v)) num_train++; break; maedhros@7353: case VEH_ROAD: if (IsRoadVehFront(v)) num_road++; break; rubidium@6585: case VEH_SHIP: num_ship++; break; rubidium@6585: case VEH_AIRCRAFT: if (IsNormalAircraft(v)) num_aircraft++; break; Darkvater@1797: default: break; Darkvater@1797: } truelight@0: } truelight@0: } truelight@0: truelight@0: FOR_ALL_VEHICLES(v) { skidd13@8450: if (v->owner == old_player && IsInsideMM(v->type, VEH_TRAIN, VEH_AIRCRAFT + 1)) { Darkvater@4848: if (new_player == PLAYER_SPECTATOR) { truelight@0: DeleteWindowById(WC_VEHICLE_VIEW, v->index); truelight@0: DeleteWindowById(WC_VEHICLE_DETAILS, v->index); truelight@0: DeleteWindowById(WC_VEHICLE_ORDERS, v->index); maedhros@7372: maedhros@7372: if (v->IsPrimaryVehicle() || (v->type == VEH_TRAIN && IsFreeWagon(v))) { maedhros@7372: switch (v->type) { maedhros@7372: default: NOT_REACHED(); maedhros@7372: maedhros@7372: case VEH_TRAIN: { maedhros@7372: Vehicle *u = v; maedhros@7372: do { maedhros@7372: Vehicle *next = GetNextVehicle(u); rubidium@7894: delete u; maedhros@7372: u = next; maedhros@7372: } while (u != NULL); maedhros@7372: } break; maedhros@7372: maedhros@7372: case VEH_ROAD: maedhros@7372: case VEH_SHIP: rubidium@7894: delete v; maedhros@7372: break; maedhros@7372: maedhros@7372: case VEH_AIRCRAFT: maedhros@7372: DeleteVehicleChain(v); maedhros@7372: break; maedhros@7372: } maedhros@7372: } truelight@0: } else { truelight@0: v->owner = new_player; glx@8682: v->colormap = PAL_NONE; rubidium@7139: v->group_id = DEFAULT_GROUP; bjarni@4621: if (IsEngineCountable(v)) GetPlayer(new_player)->num_engines[v->engine_type]++; tron@2989: switch (v->type) { rubidium@6585: case VEH_TRAIN: if (IsFrontEngine(v)) v->unitnumber = ++num_train; break; maedhros@7353: case VEH_ROAD: if (IsRoadVehFront(v)) v->unitnumber = ++num_road; break; rubidium@6585: case VEH_SHIP: v->unitnumber = ++num_ship; break; rubidium@6585: case VEH_AIRCRAFT: if (IsNormalAircraft(v)) v->unitnumber = ++num_aircraft; break; rubidium@7117: default: NOT_REACHED(); tron@2989: } truelight@0: } truelight@0: } truelight@0: } truelight@0: } truelight@0: belugas@6451: /* Change ownership of tiles */ truelight@0: { Darkvater@1797: TileIndex tile = 0; truelight@0: do { truelight@0: ChangeTileOwner(tile, old_player, new_player); tron@863: } while (++tile != MapSize()); smatz@8696: smatz@8696: if (new_player != PLAYER_SPECTATOR) { smatz@8696: /* Update all signals because there can be new segment that was owned by two players smatz@8838: * and signals were not propagated smatz@8838: * Similiar with crossings - it is needed to bar crossings that weren't before smatz@8838: * because of different owner of crossing and approaching train */ smatz@8696: tile = 0; smatz@8696: smatz@8696: do { smatz@8696: if (IsTileType(tile, MP_RAILWAY) && IsTileOwner(tile, new_player) && HasSignals(tile)) { smatz@8696: TrackBits tracks = GetTrackBits(tile); smatz@8696: do { // there may be two tracks with signals for TRACK_BIT_HORZ and TRACK_BIT_VERT smatz@8696: Track track = RemoveFirstTrack(&tracks); smatz@8802: if (HasSignalOnTrack(tile, track)) AddTrackToSignalBuffer(tile, track, new_player); smatz@8696: } while (tracks != TRACK_BIT_NONE); smatz@8838: } else if (IsLevelCrossingTile(tile) && IsTileOwner(tile, new_player)) { smatz@8838: UpdateLevelCrossing(tile); smatz@8696: } smatz@8696: } while (++tile != MapSize()); smatz@8843: } smatz@8802: smatz@8843: /* update signals in buffer */ smatz@8843: UpdateSignalsInBuffer(); truelight@0: } truelight@0: rubidium@10366: /* In all cases clear replace engine rules. rubidium@10366: * Even if it was copied, it could interfere with new owner's rules */ rubidium@10366: RemoveAllEngineReplacementForPlayer(GetPlayer(old_player)); rubidium@10366: rubidium@10366: if (new_player == PLAYER_SPECTATOR) { rubidium@10366: RemoveAllGroupsForPlayer(old_player); rubidium@10366: } else { rubidium@10366: Group *g; rubidium@10366: FOR_ALL_GROUPS(g) { rubidium@10366: if (g->owner == old_player) g->owner = new_player; rubidium@10366: } rubidium@10366: } rubidium@10366: bjarni@5077: /* Change color of existing windows */ bjarni@5077: if (new_player != PLAYER_SPECTATOR) ChangeWindowOwner(old_player, new_player); truelight@0: truelight@0: _current_player = old; truelight@0: truelight@0: MarkWholeScreenDirty(); truelight@0: } truelight@0: truelight@6588: static void ChangeNetworkOwner(PlayerID current_player, PlayerID new_player) truelight@6588: { truelight@6588: #ifdef ENABLE_NETWORK truelight@6588: if (!_networking) return; truelight@6588: truelight@6588: if (current_player == _local_player) { truelight@6588: _network_playas = new_player; truelight@6588: SetLocalPlayer(new_player); truelight@6588: } truelight@6588: truelight@6588: if (!_network_server) return; truelight@6588: truelight@6588: /* The server has to handle all administrative issues, for example truelight@6588: * updating and notifying all clients of what has happened */ truelight@6588: NetworkTCPSocketHandler *cs; truelight@6588: NetworkClientInfo *ci = NetworkFindClientInfoFromIndex(NETWORK_SERVER_INDEX); truelight@6588: truelight@6588: /* The server has just changed from player */ truelight@6588: if (current_player == ci->client_playas) { truelight@6588: ci->client_playas = new_player; truelight@6588: NetworkUpdateClientInfo(NETWORK_SERVER_INDEX); truelight@6588: } truelight@6588: truelight@6588: /* Find all clients that were in control of this company, and mark them as new_player */ truelight@6588: FOR_ALL_CLIENTS(cs) { truelight@6588: ci = DEREF_CLIENT_INFO(cs); truelight@6588: if (current_player == ci->client_playas) { truelight@6588: ci->client_playas = new_player; truelight@6588: NetworkUpdateClientInfo(ci->client_index); truelight@6588: } truelight@6588: } truelight@6588: #endif /* ENABLE_NETWORK */ truelight@6588: } truelight@6588: truelight@0: static void PlayersCheckBankrupt(Player *p) truelight@0: { tron@2475: PlayerID owner; truelight@0: belugas@6451: /* If the player has money again, it does not go bankrupt */ truelight@0: if (p->player_money >= 0) { truelight@0: p->quarters_of_bankrupcy = 0; truelight@0: return; truelight@0: } truelight@0: truelight@0: p->quarters_of_bankrupcy++; truelight@0: truelight@0: owner = p->index; truelight@0: truelight@543: switch (p->quarters_of_bankrupcy) { truelight@543: case 2: Darkvater@4873: AddNewsItem( (StringID)(owner | NB_BTROUBLE), truelight@543: NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); truelight@543: break; truelight@543: case 3: { truelight@543: /* XXX - In multiplayer, should we ask other players if it wants to take truelight@543: over when it is a human company? -- TrueLight */ Darkvater@4845: if (IsHumanPlayer(owner)) { Darkvater@4873: AddNewsItem( (StringID)(owner | NB_BTROUBLE), truelight@543: NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); truelight@543: break; truelight@543: } truelight@0: belugas@6451: /* Check if the company has any value.. if not, declare it bankrupt belugas@6451: * right now */ rubidium@7486: Money val = CalculateCompanyValue(p); truelight@543: if (val > 0) { truelight@543: p->bankrupt_value = val; truelight@543: p->bankrupt_asked = 1 << owner; // Don't ask the owner truelight@543: p->bankrupt_timeout = 0; truelight@543: break; truelight@543: } belugas@6451: /* Else, falltrue to case 4... */ truelight@0: } truelight@543: case 4: { belugas@6451: /* Close everything the owner has open */ truelight@543: DeletePlayerWindows(owner); truelight@0: belugas@6451: /* Show bankrupt news */ peter1138@7554: SetDParam(0, p->index); Darkvater@4873: AddNewsItem( (StringID)(owner | NB_BBANKRUPT), NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); truelight@543: Darkvater@5067: if (IsHumanPlayer(owner)) { Darkvater@5067: /* XXX - If we are in offline mode, leave the player playing. Eg. there Darkvater@5067: * is no THE-END, otherwise mark the player as spectator to make sure Darkvater@5067: * he/she is no long in control of this company */ Darkvater@5067: if (!_networking) { Darkvater@5067: p->bankrupt_asked = 0xFF; Darkvater@5067: p->bankrupt_timeout = 0x456; Darkvater@5067: break; Darkvater@5067: } Darkvater@5067: truelight@6588: ChangeNetworkOwner(owner, PLAYER_SPECTATOR); Darkvater@5067: } truelight@687: Darkvater@5067: /* Remove the player */ Darkvater@5067: ChangeOwnershipOfPlayerItems(owner, PLAYER_SPECTATOR); Darkvater@5067: /* Register the player as not-active */ Darkvater@5067: p->is_active = false; truelight@2395: Darkvater@5067: if (!IsHumanPlayer(owner) && (!_networking || _network_server) && _ai.enabled) Darkvater@5067: AI_PlayerDied(owner); truelight@0: } truelight@0: } truelight@0: } truelight@0: truelight@0: void DrawNewsBankrupcy(Window *w) truelight@0: { truelight@193: DrawNewsBorder(w); truelight@0: rubidium@7943: const NewsItem *ni = WP(w, news_d).ni; rubidium@7943: Player *p = GetPlayer((PlayerID)GB(ni->string_id, 0, 4)); truelight@0: DrawPlayerFace(p->face, p->player_color, 2, 23); peter1138@5919: GfxFillRect(3, 23, 3 + 91, 23 + 118, PALETTE_TO_STRUCT_GREY | (1 << USE_COLORTABLE)); truelight@0: peter1138@7554: SetDParam(0, p->index); truelight@0: truelight@0: DrawStringMultiCenter(49, 148, STR_7058_PRESIDENT, 94); truelight@0: rubidium@7943: switch (ni->string_id & 0xF0) { Darkvater@4873: case NB_BTROUBLE: belugas@8320: DrawStringCentered(w->width >> 1, 1, STR_7056_TRANSPORT_COMPANY_IN_TROUBLE, TC_FROMSTRING); truelight@0: peter1138@7554: SetDParam(0, p->index); truelight@0: truelight@0: DrawStringMultiCenter( truelight@0: ((w->width - 101) >> 1) + 98, truelight@0: 90, truelight@0: STR_7057_WILL_BE_SOLD_OFF_OR_DECLARED, truelight@0: w->width - 101); truelight@0: break; truelight@0: rubidium@7943: case NB_BMERGER: belugas@8320: DrawStringCentered(w->width >> 1, 1, STR_7059_TRANSPORT_COMPANY_MERGER, TC_FROMSTRING); rubidium@7943: SetDParam(0, ni->params[0]); rubidium@7943: SetDParam(1, p->index); rubidium@7943: SetDParam(2, ni->params[1]); truelight@0: DrawStringMultiCenter( truelight@0: ((w->width - 101) >> 1) + 98, truelight@0: 90, rubidium@7943: ni->params[1] == 0 ? STR_707F_HAS_BEEN_TAKEN_OVER_BY : STR_705A_HAS_BEEN_SOLD_TO_FOR, truelight@0: w->width - 101); truelight@0: break; truelight@0: Darkvater@4873: case NB_BBANKRUPT: belugas@8320: DrawStringCentered(w->width >> 1, 1, STR_705C_BANKRUPT, TC_FROMSTRING); rubidium@7943: SetDParam(0, ni->params[0]); truelight@0: DrawStringMultiCenter( truelight@0: ((w->width - 101) >> 1) + 98, truelight@0: 90, truelight@0: STR_705D_HAS_BEEN_CLOSED_DOWN_BY, truelight@0: w->width - 101); truelight@193: break; truelight@0: Darkvater@4873: case NB_BNEWCOMPANY: belugas@8320: DrawStringCentered(w->width >> 1, 1, STR_705E_NEW_TRANSPORT_COMPANY_LAUNCHED, TC_FROMSTRING); peter1138@7554: SetDParam(0, p->index); rubidium@7943: SetDParam(1, ni->params[0]); truelight@0: DrawStringMultiCenter( truelight@0: ((w->width - 101) >> 1) + 98, truelight@0: 90, truelight@0: STR_705F_STARTS_CONSTRUCTION_NEAR, truelight@0: w->width - 101); truelight@0: break; truelight@0: truelight@0: default: truelight@0: NOT_REACHED(); truelight@0: } truelight@0: } truelight@0: Darkvater@2436: StringID GetNewsStringBankrupcy(const NewsItem *ni) truelight@0: { rubidium@5838: const Player *p = GetPlayer((PlayerID)GB(ni->string_id, 0, 4)); truelight@0: Darkvater@4873: switch (ni->string_id & 0xF0) { Darkvater@4873: case NB_BTROUBLE: tron@534: SetDParam(0, STR_7056_TRANSPORT_COMPANY_IN_TROUBLE); tron@534: SetDParam(1, STR_7057_WILL_BE_SOLD_OFF_OR_DECLARED); peter1138@7554: SetDParam(2, p->index); truelight@0: return STR_02B6; Darkvater@4873: case NB_BMERGER: tron@534: SetDParam(0, STR_7059_TRANSPORT_COMPANY_MERGER); rubidium@7943: SetDParam(1, ni->params[1] == 0 ? STR_707F_HAS_BEEN_TAKEN_OVER_BY : STR_705A_HAS_BEEN_SOLD_TO_FOR); rubidium@7943: SetDParam(2, ni->params[0]); rubidium@7943: SetDParam(3, p->index); rubidium@7943: SetDParam(4, ni->params[1]); truelight@0: return STR_02B6; Darkvater@4873: case NB_BBANKRUPT: tron@534: SetDParam(0, STR_705C_BANKRUPT); tron@534: SetDParam(1, STR_705D_HAS_BEEN_CLOSED_DOWN_BY); rubidium@7943: SetDParam(2, ni->params[0]); truelight@0: return STR_02B6; Darkvater@4873: case NB_BNEWCOMPANY: tron@534: SetDParam(0, STR_705E_NEW_TRANSPORT_COMPANY_LAUNCHED); tron@534: SetDParam(1, STR_705F_STARTS_CONSTRUCTION_NEAR); peter1138@7554: SetDParam(2, p->index); rubidium@7943: SetDParam(3, ni->params[0]); truelight@0: return STR_02B6; truelight@0: default: truelight@0: NOT_REACHED(); truelight@0: } truelight@0: } truelight@0: rubidium@6573: static void PlayersGenStatistics() truelight@0: { truelight@0: Station *st; truelight@0: Player *p; truelight@0: truelight@0: FOR_ALL_STATIONS(st) { truelight@4346: _current_player = st->owner; rubidium@8726: CommandCost cost(EXPENSES_PROPERTY, _price.station_value >> 1); rubidium@8726: SubtractMoneyFromPlayer(cost); truelight@0: } truelight@0: skidd13@8424: if (!HasBit(1<<0|1<<3|1<<6|1<<9, _cur_month)) truelight@0: return; truelight@0: truelight@0: FOR_ALL_PLAYERS(p) { truelight@0: if (p->is_active) { Darkvater@5677: memmove(&p->old_economy[1], &p->old_economy[0], sizeof(p->old_economy) - sizeof(p->old_economy[0])); Darkvater@5677: p->old_economy[0] = p->cur_economy; truelight@0: memset(&p->cur_economy, 0, sizeof(p->cur_economy)); truelight@0: Darkvater@5677: if (p->num_valid_stat_ent != 24) p->num_valid_stat_ent++; truelight@0: dominik@116: UpdateCompanyRatingAndValue(p, true); truelight@0: PlayersCheckBankrupt(p); truelight@0: Darkvater@5677: if (p->block_preview != 0) p->block_preview--; truelight@0: } truelight@0: } truelight@0: truelight@0: InvalidateWindow(WC_INCOME_GRAPH, 0); truelight@0: InvalidateWindow(WC_OPERATING_PROFIT, 0); truelight@0: InvalidateWindow(WC_DELIVERED_CARGO, 0); truelight@0: InvalidateWindow(WC_PERFORMANCE_HISTORY, 0); truelight@0: InvalidateWindow(WC_COMPANY_VALUE, 0); truelight@0: InvalidateWindow(WC_COMPANY_LEAGUE, 0); truelight@0: } truelight@0: rubidium@7449: static void AddSingleInflation(Money *value, uint16 *frac, int32 amt) truelight@0: { rubidium@7449: /* Is it safe to add inflation ? */ rubidium@7453: if ((INT64_MAX / amt) < (*value + 1)) { rubidium@7453: *value = INT64_MAX / amt; rubidium@7449: *frac = 0; rubidium@7449: } else { rubidium@7449: int64 tmp = (int64)*value * amt + *frac; rubidium@7449: *frac = GB(tmp, 0, 16); rubidium@7449: *value += tmp >> 16; rubidium@7449: } truelight@0: } truelight@0: rubidium@6573: static void AddInflation() truelight@0: { rubidium@7740: /* The cargo payment inflation differs from the normal inflation, so the rubidium@7740: * relative amount of money you make with a transport decreases slowly over rubidium@7740: * the 170 years. After a few hundred years we reach a level in which the rubidium@7740: * games will become unplayable as the maximum income will be less than rubidium@7740: * the minimum running cost. rubidium@7740: * rubidium@7740: * Furthermore there are a lot of inflation related overflows all over the rubidium@7740: * place. Solving them is hardly possible because inflation will always rubidium@7740: * reach the overflow threshold some day. So we'll just perform the rubidium@7740: * inflation mechanism during the first 170 years (the amount of years that rubidium@7740: * one had in the original TTD) and stop doing the inflation after that rubidium@7740: * because it only causes problems that can't be solved nicely and the rubidium@7740: * inflation doesn't add anything after that either; it even makes playing rubidium@7740: * it impossible due to the diverging cost and income rates. rubidium@7740: */ rubidium@7740: if ((_cur_year - _patches.starting_year) >= (ORIGINAL_MAX_YEAR - ORIGINAL_BASE_YEAR)) return; rubidium@7740: tron@6445: /* Approximation for (100 + infl_amount)% ** (1 / 12) - 100% tron@6445: * scaled by 65536 tron@6445: * 12 -> months per year tron@6445: * This is only a good approxiamtion for small values tron@6445: */ rubidium@7449: Money inf = _economy.infl_amount * 54; truelight@0: tron@6445: for (uint i = 0; i != NUM_PRICES; i++) { rubidium@7449: AddSingleInflation((Money*)&_price + i, _price_frac + i, inf); truelight@0: } truelight@0: rubidium@7740: AddSingleInflation(&_economy.max_loan_unround, &_economy.max_loan_unround_fract, inf); truelight@193: rubidium@7740: if (_economy.max_loan + 50000 <= _economy.max_loan_unround) _economy.max_loan += 50000; truelight@0: truelight@0: inf = _economy.infl_amount_pr * 54; peter1138@6676: for (CargoID i = 0; i < NUM_CARGO; i++) { truelight@193: AddSingleInflation( rubidium@7449: (Money*)_cargo_payment_rates + i, truelight@0: _cargo_payment_rates_frac + i, truelight@0: inf truelight@0: ); truelight@0: } truelight@0: truelight@0: InvalidateWindowClasses(WC_BUILD_VEHICLE); bjarni@1098: InvalidateWindowClasses(WC_REPLACE_VEHICLE); truelight@0: InvalidateWindowClasses(WC_VEHICLE_DETAILS); truelight@0: InvalidateWindow(WC_PAYMENT_RATES, 0); truelight@0: } truelight@0: rubidium@6573: static void PlayersPayInterest() truelight@0: { tron@2548: const Player* p; truelight@0: int interest = _economy.interest_rate * 54; truelight@0: truelight@0: FOR_ALL_PLAYERS(p) { tron@2639: if (!p->is_active) continue; truelight@0: truelight@0: _current_player = p->index; truelight@193: rubidium@8726: SubtractMoneyFromPlayer(CommandCost(EXPENSES_LOAN_INT, (Money)BigMulSU(p->current_loan, interest, 16))); truelight@0: rubidium@8726: SubtractMoneyFromPlayer(CommandCost(EXPENSES_OTHER, _price.station_value >> 2)); truelight@0: } truelight@0: } truelight@0: rubidium@6573: static void HandleEconomyFluctuations() truelight@0: { tron@2639: if (_opt.diff.economy == 0) return; truelight@0: truelight@0: if (--_economy.fluct == 0) { tron@2642: _economy.fluct = -(int)GB(Random(), 0, 2); truelight@0: AddNewsItem(STR_7073_WORLD_RECESSION_FINANCIAL, NEWS_FLAGS(NM_NORMAL,0,NT_ECONOMY,0), 0, 0); truelight@0: } else if (_economy.fluct == -12) { tron@2642: _economy.fluct = GB(Random(), 0, 8) + 312; truelight@0: AddNewsItem(STR_7074_RECESSION_OVER_UPTURN_IN, NEWS_FLAGS(NM_NORMAL,0,NT_ECONOMY,0), 0, 0); truelight@0: } truelight@0: } truelight@0: truelight@0: static byte _price_category[NUM_PRICES] = { truelight@0: 0, 2, 2, 2, 2, 2, 2, 2, truelight@0: 2, 2, 2, 2, 2, 2, 2, 2, truelight@0: 2, 2, 2, 2, 2, 2, 2, 2, truelight@0: 2, 2, 2, 2, 2, 2, 2, 2, truelight@0: 2, 2, 2, 2, 2, 2, 2, 2, truelight@0: 2, 2, 1, 1, 1, 1, 1, 1, truelight@0: 2, truelight@0: }; truelight@0: rubidium@7449: static const Money _price_base[NUM_PRICES] = { belugas@6451: 100, ///< station_value belugas@6451: 100, ///< build_rail belugas@6451: 95, ///< build_road belugas@6451: 65, ///< build_signals belugas@6451: 275, ///< build_bridge belugas@6451: 600, ///< build_train_depot belugas@6451: 500, ///< build_road_depot belugas@6451: 700, ///< build_ship_depot belugas@6451: 450, ///< build_tunnel belugas@6451: 200, ///< train_station_track belugas@6451: 180, ///< train_station_length belugas@6451: 600, ///< build_airport belugas@6451: 200, ///< build_bus_station belugas@6451: 200, ///< build_truck_station belugas@6451: 350, ///< build_dock belugas@6451: 400000, ///< build_railvehicle belugas@6451: 2000, ///< build_railwagon belugas@6451: 700000, ///< aircraft_base belugas@6451: 14000, ///< roadveh_base belugas@6451: 65000, ///< ship_base belugas@6451: 20, ///< build_trees belugas@6451: 250, ///< terraform belugas@8473: 20, ///< clear_grass belugas@8473: 40, ///< clear_roughland belugas@8473: 200, ///< clear_rocks belugas@8473: 500, ///< clear_fields belugas@6451: 20, ///< remove_trees belugas@6451: -70, ///< remove_rail belugas@6451: 10, ///< remove_signals belugas@6451: 50, ///< clear_bridge belugas@6451: 80, ///< remove_train_depot belugas@6451: 80, ///< remove_road_depot belugas@6451: 90, ///< remove_ship_depot belugas@6451: 30, ///< clear_tunnel belugas@6451: 10000, ///< clear_water belugas@6451: 50, ///< remove_rail_station belugas@6451: 30, ///< remove_airport belugas@6451: 50, ///< remove_bus_station belugas@6451: 50, ///< remove_truck_station belugas@6451: 55, ///< remove_dock belugas@6451: 1600, ///< remove_house belugas@6451: 40, ///< remove_road peter1138@9120: 5600, ///< running_rail[0] steam peter1138@9120: 5200, ///< running_rail[1] diesel peter1138@9120: 4800, ///< running_rail[2] electric belugas@6451: 9600, ///< aircraft_running belugas@6451: 1600, ///< roadveh_running belugas@6451: 5600, ///< ship_running belugas@6451: 1000000, ///< build_industry truelight@0: }; truelight@0: peter1138@2506: static byte price_base_multiplier[NUM_PRICES]; peter1138@2506: peter1138@2506: /** peter1138@2506: * Reset changes to the price base multipliers. peter1138@2506: */ rubidium@6573: void ResetPriceBaseMultipliers() peter1138@2506: { peter1138@2508: uint i; peter1138@2506: belugas@6451: /* 8 means no multiplier. */ peter1138@2506: for (i = 0; i < NUM_PRICES; i++) peter1138@2506: price_base_multiplier[i] = 8; peter1138@2506: } peter1138@2506: peter1138@2506: /** peter1138@2506: * Change a price base by the given factor. peter1138@2506: * The price base is altered by factors of two, with an offset of 8. peter1138@2506: * NewBaseCost = OldBaseCost * 2^(n-8) peter1138@2506: * @param price Index of price base to change. peter1138@2506: * @param factor Amount to change by. peter1138@2506: */ peter1138@2508: void SetPriceBaseMultiplier(uint price, byte factor) peter1138@2506: { peter1138@2508: assert(price < NUM_PRICES); peter1138@2508: price_base_multiplier[price] = factor; peter1138@2506: } peter1138@2506: rubidium@6573: void StartupEconomy() truelight@0: { truelight@0: int i; truelight@0: rubidium@7449: assert(sizeof(_price) == NUM_PRICES * sizeof(Money)); truelight@0: tron@2952: for (i = 0; i != NUM_PRICES; i++) { rubidium@7449: Money price = _price_base[i]; truelight@0: if (_price_category[i] != 0) { truelight@0: uint mod = _price_category[i] == 1 ? _opt.diff.vehicle_costs : _opt.diff.construction_cost; truelight@0: if (mod < 1) { truelight@0: price = price * 3 >> 2; truelight@0: } else if (mod > 1) { truelight@0: price = price * 9 >> 3; truelight@0: } truelight@0: } peter1138@2506: if (price_base_multiplier[i] > 8) { peter1138@2506: price <<= price_base_multiplier[i] - 8; peter1138@2506: } else { peter1138@2506: price >>= 8 - price_base_multiplier[i]; peter1138@2506: } rubidium@7449: ((Money*)&_price)[i] = price; truelight@0: _price_frac[i] = 0; truelight@0: } truelight@0: truelight@0: _economy.interest_rate = _opt.diff.initial_interest; truelight@0: _economy.infl_amount = _opt.diff.initial_interest; truelight@0: _economy.infl_amount_pr = max(0, _opt.diff.initial_interest - 1); truelight@0: _economy.max_loan_unround = _economy.max_loan = _opt.diff.max_loan * 1000; tron@2150: _economy.fluct = GB(Random(), 0, 8) + 168; truelight@0: } truelight@0: peter1138@9122: peter1138@9122: Money GetPriceByIndex(uint8 index) peter1138@9122: { peter1138@9122: if (index > NUM_PRICES) return 0; peter1138@9122: peter1138@9122: return ((Money*)&_price)[index]; peter1138@9122: } peter1138@9122: peter1138@9122: tron@2630: Pair SetupSubsidyDecodeParam(const Subsidy* s, bool mode) truelight@0: { tron@1977: TileIndex tile; tron@1977: TileIndex tile2; truelight@0: Pair tp; truelight@0: tron@2272: /* if mode is false, use the singular form */ peter1138@6417: const CargoSpec *cs = GetCargo(s->cargo_type); peter1138@7783: SetDParam(0, mode ? cs->name : cs->name_single); truelight@0: truelight@0: if (s->age < 12) { peter1138@6641: if (cs->town_effect != TE_PASSENGERS && cs->town_effect != TE_MAIL) { ludde@2070: SetDParam(1, STR_INDUSTRY); ludde@2070: SetDParam(2, s->from); ludde@2070: tile = GetIndustry(s->from)->xy; truelight@0: peter1138@6641: if (cs->town_effect != TE_GOODS && cs->town_effect != TE_FOOD) { ludde@2070: SetDParam(4, STR_INDUSTRY); ludde@2070: SetDParam(5, s->to); ludde@2070: tile2 = GetIndustry(s->to)->xy; truelight@0: } else { ludde@2070: SetDParam(4, STR_TOWN); ludde@2070: SetDParam(5, s->to); ludde@2070: tile2 = GetTown(s->to)->xy; truelight@0: } truelight@0: } else { ludde@2070: SetDParam(1, STR_TOWN); ludde@2070: SetDParam(2, s->from); ludde@2070: tile = GetTown(s->from)->xy; truelight@0: ludde@2070: SetDParam(4, STR_TOWN); ludde@2070: SetDParam(5, s->to); ludde@2070: tile2 = GetTown(s->to)->xy; truelight@0: } truelight@0: } else { ludde@2070: SetDParam(1, s->from); ludde@2070: tile = GetStation(s->from)->xy; truelight@0: ludde@2070: SetDParam(2, s->to); ludde@2070: tile2 = GetStation(s->to)->xy; truelight@0: } truelight@0: truelight@0: tp.a = tile; truelight@0: tp.b = tile2; truelight@0: truelight@0: return tp; truelight@0: } truelight@0: rubidium@5566: void DeleteSubsidyWithTown(TownID index) rubidium@5566: { rubidium@5566: Subsidy *s; rubidium@5566: rubidium@5566: for (s = _subsidies; s != endof(_subsidies); s++) { peter1138@6641: if (s->cargo_type != CT_INVALID && s->age < 12) { peter1138@6641: const CargoSpec *cs = GetCargo(s->cargo_type); peter1138@6641: if (((cs->town_effect == TE_PASSENGERS || cs->town_effect == TE_MAIL) && (index == s->from || index == s->to)) || peter1138@6641: ((cs->town_effect == TE_GOODS || cs->town_effect == TE_FOOD) && index == s->to)) { peter1138@6641: s->cargo_type = CT_INVALID; peter1138@6641: } rubidium@5566: } rubidium@5566: } rubidium@5566: } rubidium@5566: rubidium@4330: void DeleteSubsidyWithIndustry(IndustryID index) truelight@0: { truelight@0: Subsidy *s; truelight@193: tron@2952: for (s = _subsidies; s != endof(_subsidies); s++) { peter1138@6641: if (s->cargo_type != CT_INVALID && s->age < 12) { peter1138@6641: const CargoSpec *cs = GetCargo(s->cargo_type); peter1138@6641: if (cs->town_effect != TE_PASSENGERS && cs->town_effect != TE_MAIL && peter1138@6641: (index == s->from || (cs->town_effect != TE_GOODS && cs->town_effect != TE_FOOD && index == s->to))) { peter1138@6641: s->cargo_type = CT_INVALID; peter1138@6641: } truelight@0: } truelight@193: } truelight@0: } truelight@0: rubidium@4330: void DeleteSubsidyWithStation(StationID index) truelight@0: { truelight@0: Subsidy *s; truelight@0: bool dirty = false; truelight@193: tron@2952: for (s = _subsidies; s != endof(_subsidies); s++) { tron@2469: if (s->cargo_type != CT_INVALID && s->age >= 12 && truelight@0: (s->from == index || s->to == index)) { tron@2469: s->cargo_type = CT_INVALID; truelight@0: dirty = true; truelight@0: } truelight@193: } truelight@0: truelight@0: if (dirty) truelight@0: InvalidateWindow(WC_SUBSIDIES_LIST, 0); truelight@0: } truelight@0: rubidium@6574: struct FoundRoute { truelight@0: uint distance; Darkvater@3344: CargoID cargo; truelight@0: void *from; truelight@0: void *to; rubidium@6574: }; truelight@0: truelight@0: static void FindSubsidyPassengerRoute(FoundRoute *fr) truelight@0: { truelight@0: Town *from,*to; truelight@0: truelight@0: fr->distance = (uint)-1; truelight@0: truelight@4356: fr->from = from = GetRandomTown(); truelight@4356: if (from == NULL || from->population < 400) return; truelight@0: truelight@4356: fr->to = to = GetRandomTown(); truelight@4356: if (from == to || to == NULL || to->population < 400 || to->pct_pass_transported > 42) truelight@0: return; truelight@0: tron@1245: fr->distance = DistanceManhattan(from->xy, to->xy); truelight@0: } truelight@0: truelight@0: static void FindSubsidyCargoRoute(FoundRoute *fr) truelight@0: { truelight@0: Industry *i; truelight@0: int trans, total; Darkvater@3344: CargoID cargo; truelight@0: truelight@0: fr->distance = (uint)-1; truelight@0: truelight@4356: fr->from = i = GetRandomIndustry(); truelight@4356: if (i == NULL) return; truelight@0: belugas@6451: /* Randomize cargo type */ skidd13@8424: if (HasBit(Random(), 0) && i->produced_cargo[1] != CT_INVALID) { glx@8141: cargo = i->produced_cargo[1]; rubidium@7315: trans = i->last_month_pct_transported[1]; rubidium@7315: total = i->last_month_production[1]; truelight@0: } else { glx@8141: cargo = i->produced_cargo[0]; rubidium@7315: trans = i->last_month_pct_transported[0]; rubidium@7315: total = i->last_month_production[0]; truelight@0: } truelight@0: belugas@6451: /* Quit if no production in this industry belugas@6451: * or if the cargo type is passengers belugas@6451: * or if the pct transported is already large enough */ peter1138@6641: if (total == 0 || trans > 42 || cargo == CT_INVALID) return; peter1138@6641: peter1138@6641: const CargoSpec *cs = GetCargo(cargo); peter1138@6641: if (cs->town_effect == TE_PASSENGERS) return; truelight@0: truelight@0: fr->cargo = cargo; truelight@0: peter1138@6641: if (cs->town_effect == TE_GOODS || cs->town_effect == TE_FOOD) { belugas@6451: /* The destination is a town */ truelight@4356: Town *t = GetRandomTown(); truelight@193: belugas@6451: /* Only want big towns */ truelight@4356: if (t == NULL || t->population < 900) return; truelight@4346: tron@1245: fr->distance = DistanceManhattan(i->xy, t->xy); truelight@0: fr->to = t; truelight@0: } else { belugas@6451: /* The destination is an industry */ truelight@4356: Industry *i2 = GetRandomIndustry(); truelight@193: belugas@6451: /* The industry must accept the cargo */ glx@8141: if (i2 == NULL || i == i2 || glx@8141: (cargo != i2->accepts_cargo[0] && glx@8141: cargo != i2->accepts_cargo[1] && glx@8141: cargo != i2->accepts_cargo[2])) { truelight@0: return; belugas@7132: } tron@1245: fr->distance = DistanceManhattan(i->xy, i2->xy); truelight@0: fr->to = i2; truelight@0: } truelight@0: } truelight@0: truelight@193: static bool CheckSubsidyDuplicate(Subsidy *s) truelight@0: { tron@2630: const Subsidy* ss; truelight@0: tron@2639: for (ss = _subsidies; ss != endof(_subsidies); ss++) { truelight@193: if (s != ss && truelight@193: ss->from == s->from && truelight@193: ss->to == s->to && truelight@0: ss->cargo_type == s->cargo_type) { tron@2469: s->cargo_type = CT_INVALID; truelight@0: return true; truelight@0: } truelight@0: } truelight@0: return false; truelight@0: } truelight@0: signde@239: rubidium@6573: static void SubsidyMonthlyHandler() truelight@0: { truelight@0: Subsidy *s; truelight@0: Pair pair; truelight@0: Station *st; truelight@0: uint n; truelight@0: FoundRoute fr; truelight@0: bool modified = false; truelight@0: tron@2952: for (s = _subsidies; s != endof(_subsidies); s++) { tron@2952: if (s->cargo_type == CT_INVALID) continue; truelight@0: truelight@0: if (s->age == 12-1) { truelight@0: pair = SetupSubsidyDecodeParam(s, 1); truelight@0: AddNewsItem(STR_202E_OFFER_OF_SUBSIDY_EXPIRED, NEWS_FLAGS(NM_NORMAL, NF_TILE, NT_SUBSIDIES, 0), pair.a, pair.b); tron@2469: s->cargo_type = CT_INVALID; truelight@0: modified = true; truelight@0: } else if (s->age == 2*12-1) { truelight@919: st = GetStation(s->to); truelight@0: if (st->owner == _local_player) { truelight@0: pair = SetupSubsidyDecodeParam(s, 1); truelight@0: AddNewsItem(STR_202F_SUBSIDY_WITHDRAWN_SERVICE, NEWS_FLAGS(NM_NORMAL, NF_TILE, NT_SUBSIDIES, 0), pair.a, pair.b); truelight@0: } tron@2469: s->cargo_type = CT_INVALID; truelight@0: modified = true; truelight@0: } else { truelight@0: s->age++; truelight@0: } truelight@0: } truelight@0: belugas@6451: /* 25% chance to go on */ skidd13@8463: if (Chance16(1,4)) { belugas@6451: /* Find a free slot*/ truelight@0: s = _subsidies; tron@2469: while (s->cargo_type != CT_INVALID) { truelight@0: if (++s == endof(_subsidies)) truelight@0: goto no_add; truelight@0: } truelight@193: truelight@0: n = 1000; truelight@0: do { truelight@0: FindSubsidyPassengerRoute(&fr); truelight@0: if (fr.distance <= 70) { truelight@0: s->cargo_type = CT_PASSENGERS; truelight@0: s->from = ((Town*)fr.from)->index; truelight@0: s->to = ((Town*)fr.to)->index; truelight@0: goto add_subsidy; truelight@0: } truelight@0: FindSubsidyCargoRoute(&fr); truelight@0: if (fr.distance <= 70) { truelight@0: s->cargo_type = fr.cargo; truelight@919: s->from = ((Industry*)fr.from)->index; peter1138@6641: { peter1138@6641: const CargoSpec *cs = GetCargo(fr.cargo); peter1138@6641: s->to = (cs->town_effect == TE_GOODS || cs->town_effect == TE_FOOD) ? ((Town*)fr.to)->index : ((Industry*)fr.to)->index; peter1138@6641: } truelight@0: add_subsidy: truelight@0: if (!CheckSubsidyDuplicate(s)) { truelight@0: s->age = 0; truelight@0: pair = SetupSubsidyDecodeParam(s, 0); truelight@0: AddNewsItem(STR_2030_SERVICE_SUBSIDY_OFFERED, NEWS_FLAGS(NM_NORMAL, NF_TILE, NT_SUBSIDIES, 0), pair.a, pair.b); truelight@0: modified = true; truelight@0: break; truelight@0: } truelight@0: } signde@239: } while (n--); truelight@0: } truelight@0: no_add:; truelight@0: if (modified) truelight@0: InvalidateWindow(WC_SUBSIDIES_LIST, 0); truelight@0: } truelight@0: Darkvater@1881: static const SaveLoad _subsidies_desc[] = { rubidium@4344: SLE_VAR(Subsidy, cargo_type, SLE_UINT8), rubidium@4344: SLE_VAR(Subsidy, age, SLE_UINT8), rubidium@4344: SLE_CONDVAR(Subsidy, from, SLE_FILE_U8 | SLE_VAR_U16, 0, 4), rubidium@4344: SLE_CONDVAR(Subsidy, from, SLE_UINT16, 5, SL_MAX_VERSION), rubidium@4344: SLE_CONDVAR(Subsidy, to, SLE_FILE_U8 | SLE_VAR_U16, 0, 4), rubidium@4344: SLE_CONDVAR(Subsidy, to, SLE_UINT16, 5, SL_MAX_VERSION), truelight@0: SLE_END() truelight@0: }; truelight@0: rubidium@6573: static void Save_SUBS() truelight@0: { truelight@0: int i; truelight@0: Subsidy *s; truelight@0: tron@2952: for (i = 0; i != lengthof(_subsidies); i++) { truelight@0: s = &_subsidies[i]; tron@2469: if (s->cargo_type != CT_INVALID) { truelight@0: SlSetArrayIndex(i); truelight@0: SlObject(s, _subsidies_desc); truelight@0: } truelight@0: } truelight@0: } truelight@0: rubidium@6573: static void Load_SUBS() truelight@0: { truelight@0: int index; truelight@0: while ((index = SlIterateArray()) != -1) truelight@0: SlObject(&_subsidies[index], _subsidies_desc); truelight@0: } truelight@0: rubidium@7450: Money GetTransportedGoodsIncome(uint num_pieces, uint dist, byte transit_days, CargoID cargo_type) truelight@0: { peter1138@6417: const CargoSpec *cs = GetCargo(cargo_type); truelight@0: peter1138@6954: /* Use callback to calculate cargo profit, if available */ skidd13@8424: if (HasBit(cs->callback_mask, CBM_CARGO_PROFIT_CALC)) { peter1138@6954: uint32 var18 = min(dist, 0xFFFF) | (min(num_pieces, 0xFF) << 16) | (transit_days << 24); peter1138@6954: uint16 callback = GetCargoCallback(CBID_CARGO_PROFIT_CALC, 0, var18, cs); peter1138@6954: if (callback != CALLBACK_FAILED) { peter1138@6954: int result = GB(callback, 0, 14); peter1138@6954: peter1138@6954: /* Simulate a 15 bit signed value */ skidd13@8424: if (HasBit(callback, 14)) result = 0x4000 - result; peter1138@6954: peter1138@6954: /* "The result should be a signed multiplier that gets multiplied peter1138@6954: * by the amount of cargo moved and the price factor, then gets peter1138@6954: * divided by 8192." */ peter1138@6954: return result * num_pieces * _cargo_payment_rates[cargo_type] / 8192; peter1138@6954: } peter1138@6954: } peter1138@6954: rubidium@7824: /* zero the distance (thus income) if it's the bank and very short transport. */ rubidium@7825: if (_opt.landscape == LT_TEMPERATE && cs->label == 'VALU' && dist < 10) return 0; truelight@193: truelight@0: rubidium@7824: static const int MIN_TIME_FACTOR = 31; rubidium@7824: static const int MAX_TIME_FACTOR = 255; rubidium@7824: rubidium@7824: const int days1 = cs->transit_days[0]; rubidium@7824: const int days2 = cs->transit_days[1]; rubidium@7824: const int days_over_days1 = transit_days - days1; rubidium@7824: rubidium@7824: /* rubidium@7824: * The time factor is calculated based on the time it took rubidium@7824: * (transit_days) compared two cargo-depending values. The rubidium@7824: * range is divided into three parts: rubidium@7824: * rubidium@7824: * - constant for fast transits rubidium@7824: * - linear decreasing with time with a slope of -1 for medium transports rubidium@7824: * - linear decreasing with time with a slope of -2 for slow transports rubidium@7824: * rubidium@7824: */ rubidium@7824: int time_factor; rubidium@7824: if (days_over_days1 <= 0) { rubidium@7824: time_factor = MAX_TIME_FACTOR; rubidium@7824: } else if (days_over_days1 <= days2) { rubidium@7824: time_factor = MAX_TIME_FACTOR - days_over_days1; rubidium@7824: } else { rubidium@7824: time_factor = MAX_TIME_FACTOR - 2 * days_over_days1 + days2; truelight@0: } truelight@0: rubidium@7824: if (time_factor < MIN_TIME_FACTOR) time_factor = MIN_TIME_FACTOR; rubidium@7824: skidd13@8422: return BigMulS(dist * time_factor * num_pieces, _cargo_payment_rates[cargo_type], 21); truelight@0: } truelight@0: Darkvater@3344: static void DeliverGoodsToIndustry(TileIndex xy, CargoID cargo_type, int num_pieces) truelight@0: { rubidium@7131: Industry *best = NULL; rubidium@7131: Industry *ind; rubidium@7131: const IndustrySpec *indspec; rubidium@7131: uint best_dist; belugas@7135: uint accepted_cargo_index = 0; ///< unlikely value, just for warning removing truelight@0: belugas@6451: /* Check if there's an industry close to the station that accepts the cargo belugas@6451: * XXX - Think of something better to belugas@6451: * 1) Only deliver to industries which are withing the catchment radius belugas@6451: * 2) Distribute between industries if more then one is present */ rubidium@7131: best_dist = (_patches.station_spread + 8) * 2; truelight@830: FOR_ALL_INDUSTRIES(ind) { rubidium@7131: indspec = GetIndustrySpec(ind->type); belugas@7135: uint i; tron@3017: glx@8141: for (i = 0; i < lengthof(ind->accepts_cargo); i++) { glx@8141: if (cargo_type == ind->accepts_cargo[i]) break; rubidium@7131: } rubidium@7131: belugas@7135: /* Check if matching cargo has been found */ glx@8141: if (i == lengthof(ind->accepts_cargo)) continue; rubidium@7131: skidd13@8424: if (HasBit(indspec->callback_flags, CBM_IND_REFUSE_CARGO)) { rubidium@7695: uint16 res = GetIndustryCallback(CBID_INDUSTRY_REFUSE_CARGO, 0, GetReverseCargoTranslation(cargo_type, indspec->grf_prop.grffile), ind, ind->type, ind->xy); rubidium@7695: if (res == 0) continue; rubidium@7695: } rubidium@7695: rubidium@7131: uint dist = DistanceManhattan(ind->xy, xy); rubidium@7131: rubidium@7131: if (dist < best_dist) { truelight@0: best = ind; rubidium@7131: best_dist = dist; rubidium@7131: accepted_cargo_index = i; truelight@0: } truelight@0: } truelight@0: truelight@0: /* Found one? */ truelight@0: if (best != NULL) { rubidium@7131: indspec = GetIndustrySpec(best->type); rubidium@7661: uint16 callback = indspec->callback_flags; rubidium@7682: truelight@0: best->was_cargo_delivered = true; rubidium@7682: best->last_cargo_accepted_at = _date; rubidium@7661: skidd13@8424: if (HasBit(callback, CBM_IND_PRODUCTION_CARGO_ARRIVAL) || HasBit(callback, CBM_IND_PRODUCTION_256_TICKS)) { rubidium@7661: best->incoming_cargo_waiting[accepted_cargo_index] = min(num_pieces + best->incoming_cargo_waiting[accepted_cargo_index], 0xFFFF); skidd13@8424: if (HasBit(callback, CBM_IND_PRODUCTION_CARGO_ARRIVAL)) { rubidium@7691: IndustryProductionCallback(best, 0); rubidium@7691: } else { rubidium@7691: InvalidateWindow(WC_INDUSTRY_VIEW, best->index); rubidium@7691: } rubidium@7661: } else { rubidium@7661: best->produced_cargo_waiting[0] = min(best->produced_cargo_waiting[0] + (num_pieces * indspec->input_cargo_multiplier[accepted_cargo_index][0] / 256), 0xFFFF); rubidium@7661: best->produced_cargo_waiting[1] = min(best->produced_cargo_waiting[1] + (num_pieces * indspec->input_cargo_multiplier[accepted_cargo_index][1] / 256), 0xFFFF); rubidium@7661: } rubidium@7725: rubidium@8356: TriggerIndustry(best, INDUSTRY_TRIGGER_RECEIVED_CARGO); rubidium@7725: StartStopIndustryTileAnimation(best, IAT_INDUSTRY_RECEIVED_CARGO); truelight@0: } truelight@0: } truelight@0: Darkvater@3344: static bool CheckSubsidised(Station *from, Station *to, CargoID cargo_type) truelight@0: { truelight@0: Subsidy *s; truelight@0: TileIndex xy; truelight@0: Pair pair; truelight@0: belugas@6451: /* check if there is an already existing subsidy that applies to us */ tron@2951: for (s = _subsidies; s != endof(_subsidies); s++) { truelight@0: if (s->cargo_type == cargo_type && truelight@0: s->age >= 12 && truelight@0: s->from == from->index && tron@2951: s->to == to->index) { truelight@0: return true; tron@2951: } truelight@0: } truelight@0: truelight@0: /* check if there's a new subsidy that applies.. */ tron@2951: for (s = _subsidies; s != endof(_subsidies); s++) { truelight@0: if (s->cargo_type == cargo_type && s->age < 12) { truelight@0: /* Check distance from source */ peter1138@6641: const CargoSpec *cs = GetCargo(cargo_type); peter1138@6641: if (cs->town_effect == TE_PASSENGERS || cs->town_effect == TE_MAIL) { truelight@919: xy = GetTown(s->from)->xy; truelight@0: } else { truelight@919: xy = (GetIndustry(s->from))->xy; truelight@0: } tron@2951: if (DistanceMax(xy, from->xy) > 9) continue; truelight@193: truelight@0: /* Check distance from dest */ peter1138@6641: switch (cs->town_effect) { peter1138@6641: case TE_PASSENGERS: peter1138@6641: case TE_MAIL: peter1138@6641: case TE_GOODS: peter1138@6641: case TE_FOOD: tron@2989: xy = GetTown(s->to)->xy; tron@2989: break; tron@2989: tron@2989: default: tron@2989: xy = GetIndustry(s->to)->xy; tron@2989: break; truelight@0: } tron@2951: if (DistanceMax(xy, to->xy) > 9) continue; truelight@0: truelight@0: /* Found a subsidy, change the values to indicate that it's in use */ truelight@0: s->age = 12; truelight@0: s->from = from->index; truelight@0: s->to = to->index; truelight@0: truelight@0: /* Add a news item */ truelight@0: pair = SetupSubsidyDecodeParam(s, 0); peter1138@7620: InjectDParam(1); truelight@0: peter1138@7620: SetDParam(0, _current_player); truelight@0: AddNewsItem( truelight@193: STR_2031_SERVICE_SUBSIDY_AWARDED + _opt.diff.subsidy_multiplier, truelight@0: NEWS_FLAGS(NM_NORMAL, NF_TILE, NT_SUBSIDIES, 0), tron@2951: pair.a, pair.b tron@2951: ); truelight@0: truelight@0: InvalidateWindow(WC_SUBSIDIES_LIST, 0); truelight@0: return true; truelight@0: } truelight@0: } truelight@0: return false; truelight@0: } truelight@0: rubidium@7486: static Money DeliverGoods(int num_pieces, CargoID cargo_type, StationID source, StationID dest, TileIndex source_tile, byte days_in_transit) truelight@0: { truelight@0: bool subsidised; truelight@0: Station *s_from, *s_to; rubidium@7486: Money profit; truelight@0: tron@4077: assert(num_pieces > 0); truelight@0: belugas@6451: /* Update player statistics */ truelight@0: { celestar@1962: Player *p = GetPlayer(_current_player); truelight@0: p->cur_economy.delivered_cargo += num_pieces; skidd13@8427: SetBit(p->cargo_types, cargo_type); truelight@0: } truelight@0: belugas@6451: /* Get station pointers. */ truelight@919: s_from = GetStation(source); truelight@919: s_to = GetStation(dest); truelight@0: belugas@6451: /* Check if a subsidy applies. */ truelight@0: subsidised = CheckSubsidised(s_from, s_to, cargo_type); truelight@0: belugas@6451: /* Increase town's counter for some special goods types */ peter1138@6641: const CargoSpec *cs = GetCargo(cargo_type); peter1138@6641: if (cs->town_effect == TE_FOOD) s_to->town->new_act_food += num_pieces; peter1138@6641: if (cs->town_effect == TE_WATER) s_to->town->new_act_water += num_pieces; truelight@0: belugas@6451: /* Give the goods to the industry. */ truelight@0: DeliverGoodsToIndustry(s_to->xy, cargo_type, num_pieces); truelight@193: belugas@6451: /* Determine profit */ celestar@5934: profit = GetTransportedGoodsIncome(num_pieces, DistanceManhattan(source_tile, s_to->xy), days_in_transit, cargo_type); truelight@0: belugas@6451: /* Modify profit if a subsidy is in effect */ truelight@0: if (subsidised) { tron@2989: switch (_opt.diff.subsidy_multiplier) { peter1138@3655: case 0: profit += profit >> 1; break; peter1138@3655: case 1: profit *= 2; break; peter1138@3655: case 2: profit *= 3; break; peter1138@3655: default: profit *= 4; break; truelight@0: } truelight@0: } truelight@0: truelight@0: return profit; truelight@0: } truelight@0: rubidium@7055: /** rubidium@7055: * Performs the vehicle payment _and_ marks the vehicle to be unloaded. rubidium@7055: * @param front_v the vehicle to be unloaded rubidium@7055: */ rubidium@7061: void VehiclePayment(Vehicle *front_v) rubidium@7055: { rubidium@7055: int result = 0; rubidium@7055: rubidium@7506: Money vehicle_profit = 0; // Money paid to the train rubidium@7506: Money route_profit = 0; // The grand total amount for the route. A-D of transfer chain A-B-C-D rubidium@7506: Money virtual_profit = 0; // The virtual profit for entire vehicle chain rubidium@7055: rubidium@7055: StationID last_visited = front_v->last_station_visited; rubidium@7055: Station *st = GetStation(last_visited); rubidium@7055: rubidium@7061: /* The owner of the train wants to be paid */ rubidium@7061: PlayerID old_player = _current_player; rubidium@7061: _current_player = front_v->owner; rubidium@7061: rubidium@7061: /* At this moment loading cannot be finished */ skidd13@8425: ClrBit(front_v->vehicle_flags, VF_LOADING_FINISHED); rubidium@7061: rubidium@7061: /* Start unloading in at the first possible moment */ rubidium@7061: front_v->load_unload_time_rem = 1; rubidium@7061: rubidium@7988: for (Vehicle *v = front_v; v != NULL; v = v->Next()) { rubidium@7061: /* No cargo to unload */ rubidium@7506: if (v->cargo_cap == 0 || v->cargo.Empty()) continue; rubidium@7055: rubidium@7061: /* All cargo has already been paid for, no need to pay again */ rubidium@7506: if (!v->cargo.UnpaidCargo()) { skidd13@8427: SetBit(v->vehicle_flags, VF_CARGO_UNLOADING); rubidium@7378: continue; rubidium@7378: } rubidium@7055: rubidium@7055: GoodsEntry *ge = &st->goods[v->cargo_type]; rubidium@7506: const CargoList::List *cargos = v->cargo.Packets(); rubidium@7378: rubidium@7506: for (CargoList::List::const_iterator it = cargos->begin(); it != cargos->end(); it++) { rubidium@7506: CargoPacket *cp = *it; rubidium@7506: if (!cp->paid_for && rubidium@7506: cp->source != last_visited && skidd13@8424: HasBit(ge->acceptance_pickup, GoodsEntry::ACCEPTANCE) && rubidium@8798: (front_v->current_order.flags & OFB_TRANSFER) == 0) { rubidium@7506: /* Deliver goods to the station */ rubidium@7506: st->time_since_unload = 0; rubidium@7055: rubidium@7506: /* handle end of route payment */ rubidium@7506: Money profit = DeliverGoods(cp->count, v->cargo_type, cp->source, last_visited, cp->source_xy, cp->days_in_transit); rubidium@7506: cp->paid_for = true; truelight@7932: route_profit += profit; // display amount paid for final route delivery, A-D of a chain A-B-C-D truelight@7932: vehicle_profit += profit - cp->feeder_share; // whole vehicle is not payed for transfers picked up earlier rubidium@7055: rubidium@7506: result |= 1; rubidium@7055: skidd13@8427: SetBit(v->vehicle_flags, VF_CARGO_UNLOADING); rubidium@8798: } else if (front_v->current_order.flags & (OFB_UNLOAD | OFB_TRANSFER)) { rubidium@8798: if (!cp->paid_for && (front_v->current_order.flags & OFB_TRANSFER) != 0) { rubidium@7506: Money profit = GetTransportedGoodsIncome( rubidium@7506: cp->count, rubidium@7506: /* pay transfer vehicle for only the part of transfer it has done: ie. cargo_loaded_at_xy to here */ rubidium@7506: DistanceManhattan(cp->loaded_at_xy, GetStation(last_visited)->xy), rubidium@7506: cp->days_in_transit, rubidium@7506: v->cargo_type); rubidium@7506: smatz@9052: front_v->profit_this_year += profit << 8; rubidium@7506: virtual_profit += profit; // accumulate transfer profits for whole vehicle rubidium@7506: cp->feeder_share += profit; // account for the (virtual) profit already made for the cargo packet rubidium@7506: cp->paid_for = true; // record that the cargo has been paid for to eliminate double counting rubidium@7506: } rubidium@7506: result |= 2; rubidium@7506: skidd13@8427: SetBit(v->vehicle_flags, VF_CARGO_UNLOADING); rubidium@7055: } rubidium@7055: } rubidium@7506: v->cargo.InvalidateCache(); rubidium@7055: } rubidium@7055: rubidium@7506: if (virtual_profit > 0) { rubidium@7506: ShowFeederIncomeAnimation(front_v->x_pos, front_v->y_pos, front_v->z_pos, virtual_profit); rubidium@7055: } rubidium@7055: rubidium@7055: if (route_profit != 0) { smatz@9052: front_v->profit_this_year += vehicle_profit << 8; rubidium@8726: SubtractMoneyFromPlayer(CommandCost(front_v->GetExpenseType(true), -route_profit)); rubidium@7055: rubidium@7055: if (IsLocalPlayer() && !PlayVehicleSound(front_v, VSE_LOAD_UNLOAD)) { rubidium@7055: SndPlayVehicleFx(SND_14_CASHTILL, front_v); rubidium@7055: } rubidium@7055: rubidium@7506: ShowCostOrIncomeAnimation(front_v->x_pos, front_v->y_pos, front_v->z_pos, -vehicle_profit); rubidium@7055: } rubidium@7055: rubidium@7061: _current_player = old_player; rubidium@7055: } rubidium@7055: rubidium@7076: /** rubidium@7076: * Loads/unload the vehicle if possible. rubidium@7076: * @param v the vehicle to be (un)loaded rubidium@7114: * @param cargo_left the amount of each cargo type that is rubidium@7114: * virtually left on the platform to be rubidium@7114: * picked up by another vehicle when all rubidium@7114: * previous vehicles have loaded. rubidium@7076: */ rubidium@7114: static void LoadUnloadVehicle(Vehicle *v, int *cargo_left) truelight@0: { rubidium@7112: assert(v->current_order.type == OT_LOADING); rubidium@7112: rubidium@7112: /* We have not waited enough time till the next round of loading/unloading */ rubidium@7114: if (--v->load_unload_time_rem != 0) { rubidium@8798: if (_patches.improved_load && HasBit(v->current_order.flags, OF_FULL_LOAD)) { rubidium@7114: /* 'Reserve' this cargo for this vehicle, because we were first. */ rubidium@7988: for (; v != NULL; v = v->Next()) { rubidium@7506: if (v->cargo_cap != 0) cargo_left[v->cargo_type] -= v->cargo_cap - v->cargo.Count(); rubidium@7114: } rubidium@7114: } rubidium@7114: return; rubidium@7114: } rubidium@7112: glx@8727: StationID last_visited = v->last_station_visited; glx@8727: Station *st = GetStation(last_visited); glx@8727: glx@8727: if (v->type == VEH_TRAIN && (!IsTileType(v->tile, MP_STATION) || GetStationIndex(v->tile) != st->index)) { rubidium@7340: /* The train reversed in the station. Take the "easy" way rubidium@7340: * out and let the train just leave as it always did. */ skidd13@8427: SetBit(v->vehicle_flags, VF_LOADING_FINISHED); rubidium@7340: return; rubidium@7340: } rubidium@7340: rubidium@7107: int unloading_time = 0; truelight@0: Vehicle *u = v; truelight@0: int result = 0; rubidium@7506: uint cap; rubidium@7061: frosch@8737: bool completely_emptied = true; rubidium@7107: bool anything_unloaded = false; rubidium@7107: bool anything_loaded = false; rubidium@7107: uint32 cargo_not_full = 0; rubidium@7107: uint32 cargo_full = 0; truelight@0: truelight@0: v->cur_speed = 0; peter1138@5251: rubidium@7988: for (; v != NULL; v = v->Next()) { rubidium@7105: if (v->cargo_cap == 0) continue; rubidium@7105: rubidium@7105: byte load_amount = EngInfo(v->engine_type)->load_amount; skidd13@8424: if (_patches.gradual_loading && HasBit(EngInfo(v->engine_type)->callbackmask, CBM_VEHICLE_LOAD_AMOUNT)) { peter1138@5211: uint16 cb_load_amount = GetVehicleCallback(CBID_VEHICLE_LOAD_AMOUNT, 0, 0, v->engine_type, v); rubidium@10330: if (cb_load_amount != CALLBACK_FAILED && GB(cb_load_amount, 0, 8) != 0) load_amount = GB(cb_load_amount, 0, 8); peter1138@5211: } tron@2639: rubidium@7105: GoodsEntry *ge = &st->goods[v->cargo_type]; truelight@193: skidd13@8424: if (HasBit(v->vehicle_flags, VF_CARGO_UNLOADING)) { rubidium@7506: uint cargo_count = v->cargo.Count(); rubidium@7506: uint amount_unloaded = _patches.gradual_loading ? min(cargo_count, load_amount) : cargo_count; rubidium@7506: bool remaining; // Are there cargo entities in this vehicle that can still be unloaded here? peter1138@5251: rubidium@8798: if (HasBit(ge->acceptance_pickup, GoodsEntry::ACCEPTANCE) && !(u->current_order.flags & OFB_TRANSFER)) { rubidium@7506: /* The cargo has reached it's final destination, the packets may now be destroyed */ rubidium@7506: remaining = v->cargo.MoveTo(NULL, amount_unloaded, CargoList::MTA_FINAL_DELIVERY, last_visited); rubidium@7506: truelight@0: result |= 1; rubidium@8798: } else if (u->current_order.flags & (OFB_UNLOAD | OFB_TRANSFER)) { rubidium@7506: remaining = v->cargo.MoveTo(&ge->cargo, amount_unloaded); skidd13@8427: SetBit(ge->acceptance_pickup, GoodsEntry::PICKUP); richk@6524: truelight@0: result |= 2; rubidium@7105: } else { rubidium@7105: /* The order changed while unloading (unset unload/transfer) or the rubidium@7105: * station does not accept goods anymore. */ skidd13@8425: ClrBit(v->vehicle_flags, VF_CARGO_UNLOADING); rubidium@7105: continue; truelight@0: } tron@445: rubidium@7105: /* Deliver goods to the station */ rubidium@7105: st->time_since_unload = 0; truelight@193: rubidium@7105: unloading_time += amount_unloaded; rubidium@7105: rubidium@7107: anything_unloaded = true; rubidium@7506: if (_patches.gradual_loading && remaining) { frosch@8737: completely_emptied = false; rubidium@7105: } else { rubidium@7105: /* We have finished unloading (cargo count == 0) */ skidd13@8425: ClrBit(v->vehicle_flags, VF_CARGO_UNLOADING); rubidium@7105: } rubidium@7105: rubidium@7105: continue; rubidium@7105: } maedhros@6139: rubidium@7105: /* Do not pick up goods that we unloaded */ rubidium@8798: if (u->current_order.flags & OFB_UNLOAD) continue; truelight@0: truelight@0: /* update stats */ rubidium@7105: int t; tron@2989: switch (u->type) { rubidium@6585: case VEH_TRAIN: t = u->u.rail.cached_max_speed; break; rubidium@6585: case VEH_ROAD: t = u->max_speed / 2; break; tron@2989: default: t = u->max_speed; break; tron@2989: } truelight@193: belugas@6451: /* if last speed is 0, we treat that as if no vehicle has ever visited the station. */ tron@2989: ge->last_speed = min(t, 255); rubidium@7486: ge->last_age = _cur_year - u->build_year; rubidium@7954: ge->days_since_pickup = 0; truelight@0: belugas@6451: /* If there's goods waiting at the station, and the vehicle belugas@6451: * has capacity for it, load it on the vehicle. */ rubidium@7506: if (!ge->cargo.Empty() && rubidium@7506: (cap = v->cargo_cap - v->cargo.Count()) != 0) { rubidium@7506: uint count = ge->cargo.Count(); celestar@1935: tron@523: /* Skip loading this vehicle if another train/vehicle is already handling tron@523: * the same cargo type at this station */ rubidium@7194: if (_patches.improved_load && cargo_left[v->cargo_type] <= 0) { skidd13@8427: SetBit(cargo_not_full, v->cargo_type); rubidium@7061: continue; rubidium@7061: } tron@523: rubidium@7114: if (cap > count) cap = count; rubidium@7114: if (_patches.gradual_loading) cap = min(cap, load_amount); rubidium@7114: if (_patches.improved_load) { rubidium@7114: /* Don't load stuff that is already 'reserved' for other vehicles */ rubidium@7519: cap = min((uint)cargo_left[v->cargo_type], cap); rubidium@7114: cargo_left[v->cargo_type] -= cap; rubidium@7114: } rubidium@7114: rubidium@7506: if (v->cargo.Empty()) TriggerVehicle(v, VEHICLE_TRIGGER_NEW_CARGO); rubidium@7114: tron@445: /* TODO: Regarding this, when we do gradual loading, we tron@445: * should first unload all vehicles and then start tron@445: * loading them. Since this will cause tron@445: * VEHICLE_TRIGGER_EMPTY to be called at the time when tron@445: * the whole vehicle chain is really totally empty, the frosch@8737: * completely_emptied assignment can then be safely tron@445: * removed; that's how TTDPatch behaves too. --pasky */ frosch@8737: completely_emptied = false; peter1138@5251: anything_loaded = true; tron@445: rubidium@7506: ge->cargo.MoveTo(&v->cargo, cap, CargoList::MTA_CARGO_LOAD, st->xy); richk@6524: truelight@0: st->time_since_load = 0; rubidium@7506: st->last_vehicle_type = v->type; truelight@193: rubidium@7970: unloading_time += cap; rubidium@7970: truelight@0: result |= 2; truelight@0: } rubidium@7107: rubidium@7506: if (v->cargo.Count() == v->cargo_cap) { skidd13@8427: SetBit(cargo_full, v->cargo_type); rubidium@7107: } else { skidd13@8427: SetBit(cargo_not_full, v->cargo_type); rubidium@7107: } truelight@0: } truelight@0: frosch@8737: /* Only set completly_emptied, if we just unloaded all remaining cargo */ frosch@8737: completely_emptied &= anything_unloaded; frosch@8737: rubidium@7114: /* We update these variables here, so gradual loading still fills rubidium@7114: * all wagons at the same time instead of using the same 'improved' rubidium@7114: * loading algorithm for the wagons (only fill wagon when there is rubidium@7114: * enough to fill the previous wagons) */ rubidium@8798: if (_patches.improved_load && HasBit(u->current_order.flags, OF_FULL_LOAD)) { rubidium@7114: /* Update left cargo */ rubidium@7988: for (v = u; v != NULL; v = v->Next()) { rubidium@7506: if (v->cargo_cap != 0) cargo_left[v->cargo_type] -= v->cargo_cap - v->cargo.Count(); rubidium@7114: } rubidium@7114: } rubidium@7114: peter1138@5211: v = u; celestar@1935: rubidium@7107: if (anything_loaded || anything_unloaded) { rubidium@7107: if (_patches.gradual_loading) { rubidium@7107: /* The time it takes to load one 'slice' of cargo or passengers depends rubidium@7107: * on the vehicle type - the values here are those found in TTDPatch */ rubidium@7107: const uint gradual_loading_wait_time[] = { 40, 20, 10, 20 }; peter1138@5211: rubidium@7107: unloading_time = gradual_loading_wait_time[v->type]; rubidium@7107: } rubidium@7107: } else { rubidium@7107: bool finished_loading = true; rubidium@8798: if (HasBit(v->current_order.flags, OF_FULL_LOAD)) { rubidium@7107: if (_patches.full_load_any) { rubidium@7107: /* if the aircraft carries passengers and is NOT full, then rubidium@7107: * continue loading, no matter how much mail is in */ rubidium@7506: if ((v->type == VEH_AIRCRAFT && IsCargoInClass(v->cargo_type, CC_PASSENGERS) && v->cargo_cap != v->cargo.Count()) || rubidium@7107: (cargo_not_full && (cargo_full & ~cargo_not_full) == 0)) { // There are stull non-full cargos rubidium@7107: finished_loading = false; rubidium@7107: } rubidium@7107: } else if (cargo_not_full != 0) { rubidium@7107: finished_loading = false; peter1138@5251: } peter1138@5251: } rubidium@7107: unloading_time = 20; rubidium@7107: rubidium@7107: SB(v->vehicle_flags, VF_LOADING_FINISHED, 1, finished_loading); peter1138@5211: } truelight@0: rubidium@6585: if (v->type == VEH_TRAIN) { belugas@6451: /* Each platform tile is worth 2 rail vehicles. */ celestar@6324: int overhang = v->u.rail.cached_total_length - st->GetPlatformLength(v->tile) * TILE_SIZE; peter1138@2587: if (overhang > 0) { peter1138@2587: unloading_time <<= 1; peter1138@2587: unloading_time += (overhang * unloading_time) / 8; truelight@0: } truelight@0: } truelight@0: rubidium@8039: /* Calculate the loading indicator fill percent and display rubidium@8039: * In the Game Menu do not display indicators rubidium@8039: * If _patches.loading_indicators == 2, show indicators (bool can be promoted to int as 0 or 1 - results in 2 > 0,1 ) rubidium@8039: * if _patches.loading_indicators == 1, _local_player must be the owner or must be a spectator to show ind., so 1 > 0 rubidium@8039: * if _patches.loading_indicators == 0, do not display indicators ... 0 is never greater than anything rubidium@8039: */ glx@8059: if (_game_mode != GM_MENU && (_patches.loading_indicators > (uint)(v->owner != _local_player && _local_player != PLAYER_SPECTATOR))) { truelight@7510: StringID percent_up_down = STR_NULL; truelight@7510: int percent = CalcPercentVehicleFilled(v, &percent_up_down); truelight@7494: if (v->fill_percent_te_id == INVALID_TE_ID) { truelight@7510: v->fill_percent_te_id = ShowFillingPercent(v->x_pos, v->y_pos, v->z_pos + 20, percent, percent_up_down); truelight@7494: } else { truelight@7510: UpdateFillingPercent(v->fill_percent_te_id, percent, percent_up_down); truelight@7494: } truelight@7494: } truelight@7494: truelight@0: v->load_unload_time_rem = unloading_time; truelight@0: frosch@8737: if (completely_emptied) { tron@445: TriggerVehicle(v, VEHICLE_TRIGGER_EMPTY); tron@445: } tron@445: truelight@0: if (result != 0) { rubidium@7061: InvalidateWindow(v->GetVehicleListWindowClass(), v->owner); truelight@0: InvalidateWindow(WC_VEHICLE_DETAILS, v->index); rubidium@7061: peter1138@7319: st->MarkTilesDirty(true); rubidium@7061: v->MarkDirty(); truelight@0: tron@2951: if (result & 2) InvalidateWindow(WC_STATION_VIEW, last_visited); truelight@0: } truelight@0: } truelight@0: rubidium@7112: /** rubidium@7112: * Load/unload the vehicles in this station according to the order rubidium@7112: * they entered. rubidium@7112: * @param st the station to do the loading/unloading for rubidium@7112: */ rubidium@7112: void LoadUnloadStation(Station *st) rubidium@7112: { rubidium@7114: int cargo_left[NUM_CARGO]; rubidium@7114: rubidium@7506: for (uint i = 0; i < NUM_CARGO; i++) cargo_left[i] = st->goods[i].cargo.Count(); rubidium@7114: rubidium@7112: std::list::iterator iter; rubidium@7112: for (iter = st->loading_vehicles.begin(); iter != st->loading_vehicles.end(); ++iter) { rubidium@7112: Vehicle *v = *iter; rubidium@7114: if (!(v->vehstatus & (VS_STOPPED | VS_CRASHED))) LoadUnloadVehicle(v, cargo_left); rubidium@7112: } rubidium@7112: } rubidium@7112: rubidium@6573: void PlayersMonthlyLoop() truelight@0: { truelight@0: PlayersGenStatistics(); rubidium@4293: if (_patches.inflation && _cur_year < MAX_YEAR) truelight@0: AddInflation(); truelight@0: PlayersPayInterest(); belugas@6451: /* Reset the _current_player flag */ signde@206: _current_player = OWNER_NONE; truelight@0: HandleEconomyFluctuations(); truelight@0: SubsidyMonthlyHandler(); truelight@0: } truelight@0: truelight@0: static void DoAcquireCompany(Player *p) truelight@0: { truelight@0: Player *owner; rubidium@5838: int i; rubidium@7449: Money value; truelight@0: peter1138@7554: SetDParam(0, p->index); peter1138@7554: SetDParam(1, p->bankrupt_value); Darkvater@4873: AddNewsItem( (StringID)(_current_player | NB_BMERGER), NEWS_FLAGS(NM_CALLBACK, 0, NT_COMPANY_INFO, DNC_BANKRUPCY),0,0); truelight@0: belugas@6451: /* original code does this a little bit differently */ rubidium@5838: PlayerID pi = p->index; truelight@6588: ChangeNetworkOwner(pi, _current_player); truelight@0: ChangeOwnershipOfPlayerItems(pi, _current_player); truelight@0: truelight@0: if (p->bankrupt_value == 0) { celestar@1962: owner = GetPlayer(_current_player); truelight@0: owner->current_loan += p->current_loan; truelight@0: } truelight@0: truelight@0: value = CalculateCompanyValue(p) >> 2; rubidium@7449: PlayerID old_player = _current_player; tron@2952: for (i = 0; i != 4; i++) { Darkvater@4848: if (p->share_owners[i] != PLAYER_SPECTATOR) { rubidium@7449: _current_player = p->share_owners[i]; rubidium@8726: SubtractMoneyFromPlayer(CommandCost(EXPENSES_OTHER, -value)); truelight@0: } truelight@0: } rubidium@7449: _current_player = old_player; truelight@0: truelight@0: p->is_active = false; truelight@0: truelight@0: DeletePlayerWindows(pi); rubidium@4434: RebuildVehicleLists(); //Updates the open windows to add the newly acquired vehicles to the lists truelight@0: } truelight@0: rubidium@5838: extern int GetAmountOwnedBy(const Player *p, PlayerID owner); truelight@599: Darkvater@1793: /** Acquire shares in an opposing company. tron@3491: * @param tile unused belugas@6928: * @param flags type of operation Darkvater@1793: * @param p1 player to buy the shares from Darkvater@1793: * @param p2 unused Darkvater@1793: */ rubidium@7439: CommandCost CmdBuyShareInCompany(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@0: Player *p; rubidium@8726: CommandCost cost(EXPENSES_OTHER); Darkvater@1793: rubidium@7999: /* Check if buying shares is allowed (protection against modified clients) */ rubidium@7999: /* Cannot buy own shares */ rubidium@7999: if (!IsValidPlayer((PlayerID)p1) || !_patches.allow_shares || _current_player == (PlayerID)p1) return CMD_ERROR; rubidium@7999: rubidium@7999: p = GetPlayer((PlayerID)p1); rubidium@7999: rubidium@7999: /* Cannot buy shares of non-existent nor bankrupted company */ rubidium@7999: if (!p->is_active) return CMD_ERROR; truelight@0: Darkvater@1793: /* Protect new companies from hostile takeovers */ maedhros@8920: if (_cur_year - p->inaugurated_year < 6) return_cmd_error(STR_PROTECTED); darkvater@930: truelight@599: /* Those lines are here for network-protection (clients can be slow) */ rubidium@7446: if (GetAmountOwnedBy(p, PLAYER_SPECTATOR) == 0) return cost; darkvater@930: Darkvater@1793: /* We can not buy out a real player (temporarily). TODO: well, enable it obviously */ rubidium@7446: if (GetAmountOwnedBy(p, PLAYER_SPECTATOR) == 1 && !p->is_ai) return cost; tron@1019: rubidium@7446: cost.AddCost(CalculateCompanyValue(p) >> 2); truelight@0: if (flags & DC_EXEC) { rubidium@5838: PlayerByte* b = p->share_owners; Darkvater@1793: int i; Darkvater@1793: Darkvater@4848: while (*b != PLAYER_SPECTATOR) b++; /* share owners is guaranteed to contain at least one PLAYER_SPECTATOR */ truelight@0: *b = _current_player; truelight@0: Darkvater@1793: for (i = 0; p->share_owners[i] == _current_player;) { truelight@0: if (++i == 4) { truelight@0: p->bankrupt_value = 0; truelight@0: DoAcquireCompany(p); truelight@0: break; truelight@0: } truelight@0: } tron@3017: InvalidateWindow(WC_COMPANY, p1); truelight@0: } truelight@0: return cost; truelight@0: } truelight@0: Darkvater@1793: /** Sell shares in an opposing company. tron@3491: * @param tile unused belugas@6928: * @param flags type of operation Darkvater@1793: * @param p1 player to sell the shares from Darkvater@1793: * @param p2 unused Darkvater@1793: */ rubidium@7439: CommandCost CmdSellShareInCompany(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@0: Player *p; rubidium@7449: Money cost; Darkvater@1793: rubidium@7999: /* Check if selling shares is allowed (protection against modified clients) */ rubidium@7999: /* Cannot sell own shares */ rubidium@7999: if (!IsValidPlayer((PlayerID)p1) || !_patches.allow_shares || _current_player == (PlayerID)p1) return CMD_ERROR; rubidium@7999: rubidium@7999: p = GetPlayer((PlayerID)p1); rubidium@7999: rubidium@7999: /* Cannot sell shares of non-existent nor bankrupted company */ rubidium@7999: if (!p->is_active) return CMD_ERROR; truelight@0: truelight@653: /* Those lines are here for network-protection (clients can be slow) */ rubidium@7446: if (GetAmountOwnedBy(p, _current_player) == 0) return CommandCost(); truelight@653: truelight@0: /* adjust it a little to make it less profitable to sell and buy */ truelight@0: cost = CalculateCompanyValue(p) >> 2; truelight@0: cost = -(cost - (cost >> 7)); truelight@0: truelight@0: if (flags & DC_EXEC) { rubidium@5838: PlayerByte* b = p->share_owners; belugas@6451: while (*b != _current_player) b++; // share owners is guaranteed to contain player Darkvater@4848: *b = PLAYER_SPECTATOR; tron@3017: InvalidateWindow(WC_COMPANY, p1); truelight@0: } rubidium@8726: return CommandCost(EXPENSES_OTHER, cost); truelight@0: } truelight@0: Darkvater@1793: /** Buy up another company. Darkvater@1793: * When a competing company is gone bankrupt you get the chance to purchase Darkvater@1793: * that company. Darkvater@1793: * @todo currently this only works for AI players tron@3491: * @param tile unused belugas@6928: * @param flags type of operation Darkvater@1793: * @param p1 player/company to buy up Darkvater@1793: * @param p2 unused Darkvater@1793: */ rubidium@7439: CommandCost CmdBuyCompany(TileIndex tile, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@0: Player *p; celestar@6901: PlayerID pid = (PlayerID)p1; Darkvater@1793: Darkvater@1793: /* Disable takeovers in multiplayer games */ celestar@6901: if (!IsValidPlayer(pid) || _networking) return CMD_ERROR; celestar@6901: celestar@6901: /* Do not allow players to take over themselves */ celestar@6901: if (pid == _current_player) return CMD_ERROR; Darkvater@1793: celestar@6901: p = GetPlayer(pid); Darkvater@1793: Darkvater@1793: if (!p->is_ai) return CMD_ERROR; Darkvater@1793: Darkvater@1793: if (flags & DC_EXEC) { truelight@0: DoAcquireCompany(p); truelight@0: } rubidium@8726: return CommandCost(EXPENSES_OTHER, p->bankrupt_value); truelight@0: } truelight@0: belugas@6451: /** Prices */ rubidium@6573: static void SaveLoad_PRIC() truelight@0: { rubidium@7451: int vt = CheckSavegameVersion(65) ? (SLE_FILE_I32 | SLE_VAR_I64) : SLE_INT64; rubidium@7451: SlArray(&_price, NUM_PRICES, vt); truelight@0: SlArray(&_price_frac, NUM_PRICES, SLE_UINT16); truelight@0: } truelight@0: belugas@6451: /** Cargo payment rates */ rubidium@6573: static void SaveLoad_CAPR() truelight@0: { peter1138@6959: uint num_cargo = CheckSavegameVersion(55) ? 12 : NUM_CARGO; rubidium@7451: int vt = CheckSavegameVersion(65) ? (SLE_FILE_I32 | SLE_VAR_I64) : SLE_INT64; rubidium@7451: SlArray(&_cargo_payment_rates, num_cargo, vt); peter1138@6959: SlArray(&_cargo_payment_rates_frac, num_cargo, SLE_UINT16); truelight@0: } truelight@0: Darkvater@1881: static const SaveLoad _economy_desc[] = { rubidium@7451: SLE_CONDVAR(Economy, max_loan, SLE_FILE_I32 | SLE_VAR_I64, 0, 64), rubidium@7451: SLE_CONDVAR(Economy, max_loan, SLE_INT64, 65, SL_MAX_VERSION), rubidium@7451: SLE_CONDVAR(Economy, max_loan_unround, SLE_FILE_I32 | SLE_VAR_I64, 0, 64), rubidium@7451: SLE_CONDVAR(Economy, max_loan_unround, SLE_INT64, 65, SL_MAX_VERSION), rubidium@7740: SLE_CONDVAR(Economy, max_loan_unround_fract, SLE_UINT16, 70, SL_MAX_VERSION), rubidium@7451: SLE_VAR(Economy, fluct, SLE_FILE_I16 | SLE_VAR_I32), rubidium@7451: SLE_VAR(Economy, interest_rate, SLE_UINT8), rubidium@7451: SLE_VAR(Economy, infl_amount, SLE_UINT8), rubidium@7451: SLE_VAR(Economy, infl_amount_pr, SLE_UINT8), rubidium@7451: SLE_END() truelight@0: }; truelight@0: belugas@6451: /** Economy variables */ rubidium@6573: static void SaveLoad_ECMY() truelight@0: { Darkvater@1881: SlObject(&_economy, _economy_desc); truelight@0: } truelight@0: rubidium@5838: extern const ChunkHandler _economy_chunk_handlers[] = { truelight@0: { 'PRIC', SaveLoad_PRIC, SaveLoad_PRIC, CH_RIFF | CH_AUTO_LENGTH}, truelight@0: { 'CAPR', SaveLoad_CAPR, SaveLoad_CAPR, CH_RIFF | CH_AUTO_LENGTH}, rubidium@4344: { 'SUBS', Save_SUBS, Load_SUBS, CH_ARRAY}, truelight@0: { 'ECMY', SaveLoad_ECMY, SaveLoad_ECMY, CH_RIFF | CH_LAST}, truelight@0: };