subsidy.cpp

Go to the documentation of this file.
00001 /* $Id$ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include "company_func.h"
00014 #include "industry.h"
00015 #include "town.h"
00016 #include "news_func.h"
00017 #include "ai/ai.hpp"
00018 #include "station_base.h"
00019 #include "cargotype.h"
00020 #include "strings_func.h"
00021 #include "window_func.h"
00022 #include "subsidy_base.h"
00023 #include "subsidy_func.h"
00024 #include "core/pool_func.hpp"
00025 #include "core/random_func.hpp"
00026 
00027 #include "table/strings.h"
00028 
00029 SubsidyPool _subsidy_pool("Subsidy");
00030 INSTANTIATE_POOL_METHODS(Subsidy)
00031 
00032 
00036 void Subsidy::AwardTo(CompanyID company)
00037 {
00038   assert(!this->IsAwarded());
00039 
00040   this->awarded = company;
00041   this->remaining = SUBSIDY_CONTRACT_MONTHS;
00042 
00043   char company_name[MAX_LENGTH_COMPANY_NAME_CHARS * MAX_CHAR_LENGTH];
00044   SetDParam(0, company);
00045   GetString(company_name, STR_COMPANY_NAME, lastof(company_name));
00046 
00047   char *cn = strdup(company_name);
00048 
00049   /* Add a news item */
00050   Pair reftype = SetupSubsidyDecodeParam(this, false);
00051   InjectDParam(1);
00052 
00053   SetDParamStr(0, cn);
00054   AddNewsItem(
00055     STR_NEWS_SERVICE_SUBSIDY_AWARDED_HALF + _settings_game.difficulty.subsidy_multiplier,
00056     NS_SUBSIDIES,
00057     (NewsReferenceType)reftype.a, this->src, (NewsReferenceType)reftype.b, this->dst,
00058     cn
00059   );
00060   AI::BroadcastNewEvent(new ScriptEventSubsidyAwarded(this->index));
00061 
00062   InvalidateWindowData(WC_SUBSIDIES_LIST, 0);
00063 }
00064 
00065 Pair SetupSubsidyDecodeParam(const Subsidy *s, bool mode)
00066 {
00067   NewsReferenceType reftype1 = NR_NONE;
00068   NewsReferenceType reftype2 = NR_NONE;
00069 
00070   /* if mode is false, use the singular form */
00071   const CargoSpec *cs = CargoSpec::Get(s->cargo_type);
00072   SetDParam(0, mode ? cs->name : cs->name_single);
00073 
00074   switch (s->src_type) {
00075     case ST_INDUSTRY:
00076       reftype1 = NR_INDUSTRY;
00077       SetDParam(1, STR_INDUSTRY_NAME);
00078       break;
00079     case ST_TOWN:
00080       reftype1 = NR_TOWN;
00081       SetDParam(1, STR_TOWN_NAME);
00082       break;
00083     default: NOT_REACHED();
00084   }
00085   SetDParam(2, s->src);
00086 
00087   switch (s->dst_type) {
00088     case ST_INDUSTRY:
00089       reftype2 = NR_INDUSTRY;
00090       SetDParam(4, STR_INDUSTRY_NAME);
00091       break;
00092     case ST_TOWN:
00093       reftype2 = NR_TOWN;
00094       SetDParam(4, STR_TOWN_NAME);
00095       break;
00096     default: NOT_REACHED();
00097   }
00098   SetDParam(5, s->dst);
00099 
00100   Pair p;
00101   p.a = reftype1;
00102   p.b = reftype2;
00103   return p;
00104 }
00105 
00112 static inline void SetPartOfSubsidyFlag(SourceType type, SourceID index, PartOfSubsidy flag)
00113 {
00114   switch (type) {
00115     case ST_INDUSTRY: Industry::Get(index)->part_of_subsidy |= flag; return;
00116     case ST_TOWN:         Town::Get(index)->part_of_subsidy |= flag; return;
00117     default: NOT_REACHED();
00118   }
00119 }
00120 
00121 void RebuildSubsidisedSourceAndDestinationCache()
00122 {
00123   Town *t;
00124   FOR_ALL_TOWNS(t) t->part_of_subsidy = POS_NONE;
00125 
00126   Industry *i;
00127   FOR_ALL_INDUSTRIES(i) i->part_of_subsidy = POS_NONE;
00128 
00129   const Subsidy *s;
00130   FOR_ALL_SUBSIDIES(s) {
00131     SetPartOfSubsidyFlag(s->src_type, s->src, POS_SRC);
00132     SetPartOfSubsidyFlag(s->dst_type, s->dst, POS_DST);
00133   }
00134 }
00135 
00136 void DeleteSubsidyWith(SourceType type, SourceID index)
00137 {
00138   bool dirty = false;
00139 
00140   Subsidy *s;
00141   FOR_ALL_SUBSIDIES(s) {
00142     if ((s->src_type == type && s->src == index) || (s->dst_type == type && s->dst == index)) {
00143       delete s;
00144       dirty = true;
00145     }
00146   }
00147 
00148   if (dirty) {
00149     InvalidateWindowData(WC_SUBSIDIES_LIST, 0);
00150     RebuildSubsidisedSourceAndDestinationCache();
00151   }
00152 }
00153 
00154 static bool CheckSubsidyDuplicate(CargoID cargo, SourceType src_type, SourceID src, SourceType dst_type, SourceID dst)
00155 {
00156   const Subsidy *s;
00157   FOR_ALL_SUBSIDIES(s) {
00158     if (s->cargo_type == cargo &&
00159         s->src_type == src_type && s->src == src &&
00160         s->dst_type == dst_type && s->dst == dst) {
00161       return true;
00162     }
00163   }
00164   return false;
00165 }
00166 
00174 static bool CheckSubsidyDistance(SourceType src_type, SourceID src, SourceType dst_type, SourceID dst)
00175 {
00176   TileIndex tile_src = (src_type == ST_TOWN) ? Town::Get(src)->xy : Industry::Get(src)->location.tile;
00177   TileIndex tile_dst = (dst_type == ST_TOWN) ? Town::Get(dst)->xy : Industry::Get(dst)->location.tile;
00178 
00179   return (DistanceManhattan(tile_src, tile_dst) <= SUBSIDY_MAX_DISTANCE);
00180 }
00181 
00189 void CreateSubsidy(CargoID cid, SourceType src_type, SourceID src, SourceType dst_type, SourceID dst)
00190 {
00191   Subsidy *s = new Subsidy();
00192   s->cargo_type = cid;
00193   s->src_type = src_type;
00194   s->src = src;
00195   s->dst_type = dst_type;
00196   s->dst = dst;
00197   s->remaining = SUBSIDY_OFFER_MONTHS;
00198   s->awarded = INVALID_COMPANY;
00199 
00200   Pair reftype = SetupSubsidyDecodeParam(s, false);
00201   AddNewsItem(STR_NEWS_SERVICE_SUBSIDY_OFFERED, NS_SUBSIDIES, (NewsReferenceType)reftype.a, s->src, (NewsReferenceType)reftype.b, s->dst);
00202   SetPartOfSubsidyFlag(s->src_type, s->src, POS_SRC);
00203   SetPartOfSubsidyFlag(s->dst_type, s->dst, POS_DST);
00204   AI::BroadcastNewEvent(new ScriptEventSubsidyOffer(s->index));
00205 }
00206 
00207 
00211 bool FindSubsidyPassengerRoute()
00212 {
00213   if (!Subsidy::CanAllocateItem()) return false;
00214 
00215   const Town *src = Town::GetRandom();
00216   if (src->population < SUBSIDY_PAX_MIN_POPULATION ||
00217       src->GetPercentTransported(CT_PASSENGERS) > SUBSIDY_MAX_PCT_TRANSPORTED) {
00218     return false;
00219   }
00220 
00221   const Town *dst = Town::GetRandom();
00222   if (dst->population < SUBSIDY_PAX_MIN_POPULATION || src == dst) {
00223     return false;
00224   }
00225 
00226   if (DistanceManhattan(src->xy, dst->xy) > SUBSIDY_MAX_DISTANCE) return false;
00227   if (CheckSubsidyDuplicate(CT_PASSENGERS, ST_TOWN, src->index, ST_TOWN, dst->index)) return false;
00228 
00229   CreateSubsidy(CT_PASSENGERS, ST_TOWN, src->index, ST_TOWN, dst->index);
00230 
00231   return true;
00232 }
00233 
00234 bool FindSubsidyCargoDestination(CargoID cid, SourceType src_type, SourceID src);
00235 
00236 
00240 bool FindSubsidyTownCargoRoute()
00241 {
00242   if (!Subsidy::CanAllocateItem()) return false;
00243 
00244   SourceType src_type = ST_TOWN;
00245 
00246   /* Select a random town. */
00247   const Town *src_town = Town::GetRandom();
00248 
00249   uint32 town_cargo_produced = src_town->cargo_produced;
00250 
00251   /* Passenger subsidies are not handled here. */
00252   ClrBit(town_cargo_produced, CT_PASSENGERS);
00253 
00254   /* Choose a random cargo that is produced in the town. */
00255   uint8 cargo_number = RandomRange(CountBits(town_cargo_produced));
00256   CargoID cid;
00257   FOR_EACH_SET_CARGO_ID(cid, town_cargo_produced) {
00258     if (cargo_number == 0) break;
00259     cargo_number--;
00260   }
00261 
00262   /* Avoid using invalid NewGRF cargos. */
00263   if (!CargoSpec::Get(cid)->IsValid()) return false;
00264 
00265   /* Quit if the percentage transported is large enough. */
00266   if (src_town->GetPercentTransported(cid) > SUBSIDY_MAX_PCT_TRANSPORTED) return false;
00267 
00268   SourceID src = src_town->index;
00269 
00270   return FindSubsidyCargoDestination(cid, src_type, src);
00271 }
00272 
00276 bool FindSubsidyIndustryCargoRoute()
00277 {
00278   if (!Subsidy::CanAllocateItem()) return false;
00279 
00280   SourceType src_type = ST_INDUSTRY;
00281 
00282   /* Select a random industry. */
00283   const Industry *src_ind = Industry::GetRandom();
00284   if (src_ind == NULL) return false;
00285 
00286   uint trans, total;
00287 
00288   CargoID cid;
00289 
00290   /* Randomize cargo type */
00291   if (src_ind->produced_cargo[1] != CT_INVALID && HasBit(Random(), 0)) {
00292     cid = src_ind->produced_cargo[1];
00293     trans = src_ind->last_month_pct_transported[1];
00294     total = src_ind->last_month_production[1];
00295   } else {
00296     cid = src_ind->produced_cargo[0];
00297     trans = src_ind->last_month_pct_transported[0];
00298     total = src_ind->last_month_production[0];
00299   }
00300 
00301   /* Quit if no production in this industry
00302    * or if the pct transported is already large enough */
00303   if (total == 0 || trans > SUBSIDY_MAX_PCT_TRANSPORTED || cid == CT_INVALID) return false;
00304 
00305   SourceID src = src_ind->index;
00306 
00307   return FindSubsidyCargoDestination(cid, src_type, src);
00308 }
00309 
00316 bool FindSubsidyCargoDestination(CargoID cid, SourceType src_type, SourceID src)
00317 {
00318   /* Choose a random destination. Only consider towns if they can accept the cargo. */
00319   SourceType dst_type = (HasBit(_town_cargos_accepted, cid) && Chance16(1, 2)) ? ST_TOWN : ST_INDUSTRY;
00320 
00321   SourceID dst;
00322   switch (dst_type) {
00323     case ST_TOWN: {
00324       /* Select a random town. */
00325       const Town *dst_town = Town::GetRandom();
00326 
00327       /* Check if the town can accept this cargo. */
00328       if (!HasBit(dst_town->cargo_accepted_total, cid)) return false;
00329 
00330       dst = dst_town->index;
00331       break;
00332     }
00333 
00334     case ST_INDUSTRY: {
00335       /* Select a random industry. */
00336       const Industry *dst_ind = Industry::GetRandom();
00337 
00338       /* The industry must accept the cargo */
00339       if (dst_ind == NULL ||
00340           (cid != dst_ind->accepts_cargo[0] &&
00341            cid != dst_ind->accepts_cargo[1] &&
00342            cid != dst_ind->accepts_cargo[2])) {
00343         return false;
00344       }
00345 
00346       dst = dst_ind->index;
00347       break;
00348     }
00349 
00350     default: NOT_REACHED();
00351   }
00352 
00353   /* Check that the source and the destination are not the same. */
00354   if (src_type == dst_type && src == dst) return false;
00355 
00356   /* Check distance between source and destination. */
00357   if (!CheckSubsidyDistance(src_type, src, dst_type, dst)) return false;
00358 
00359   /* Avoid duplicate subsidies. */
00360   if (CheckSubsidyDuplicate(cid, src_type, src, dst_type, dst)) return false;
00361 
00362   CreateSubsidy(cid, src_type, src, dst_type, dst);
00363 
00364   return true;
00365 }
00366 
00367 void SubsidyMonthlyLoop()
00368 {
00369   bool modified = false;
00370 
00371   Subsidy *s;
00372   FOR_ALL_SUBSIDIES(s) {
00373     if (--s->remaining == 0) {
00374       if (!s->IsAwarded()) {
00375         Pair reftype = SetupSubsidyDecodeParam(s, true);
00376         AddNewsItem(STR_NEWS_OFFER_OF_SUBSIDY_EXPIRED, NS_SUBSIDIES, (NewsReferenceType)reftype.a, s->src, (NewsReferenceType)reftype.b, s->dst);
00377         AI::BroadcastNewEvent(new ScriptEventSubsidyOfferExpired(s->index));
00378       } else {
00379         if (s->awarded == _local_company) {
00380           Pair reftype = SetupSubsidyDecodeParam(s, true);
00381           AddNewsItem(STR_NEWS_SUBSIDY_WITHDRAWN_SERVICE, NS_SUBSIDIES, (NewsReferenceType)reftype.a, s->src, (NewsReferenceType)reftype.b, s->dst);
00382         }
00383         AI::BroadcastNewEvent(new ScriptEventSubsidyExpired(s->index));
00384       }
00385       delete s;
00386       modified = true;
00387     }
00388   }
00389 
00390   if (modified) RebuildSubsidisedSourceAndDestinationCache();
00391 
00392   bool passenger_subsidy = false;
00393   bool town_subsidy = false;
00394   bool industry_subsidy = false;
00395 
00396   int random_chance = RandomRange(16);
00397 
00398   if (random_chance < 2) {
00399     /* There is a 1/8 chance each month of generating a passenger subsidy. */
00400     int n = 1000;
00401 
00402     do {
00403       passenger_subsidy = FindSubsidyPassengerRoute();
00404     } while (!passenger_subsidy && n--);
00405   } else if (random_chance == 2) {
00406     /* Cargo subsidies with a town as a source have a 1/16 chance. */
00407     int n = 1000;
00408 
00409     do {
00410       town_subsidy = FindSubsidyTownCargoRoute();
00411     } while (!town_subsidy && n--);
00412   } else if (random_chance == 3) {
00413     /* Cargo subsidies with an industry as a source have a 1/16 chance. */
00414     int n = 1000;
00415 
00416     do {
00417       industry_subsidy = FindSubsidyTownCargoRoute();
00418     } while (!industry_subsidy && n--);
00419   }
00420 
00421   modified |= passenger_subsidy || town_subsidy || industry_subsidy;
00422 
00423   if (modified) InvalidateWindowData(WC_SUBSIDIES_LIST, 0);
00424 }
00425 
00435 bool CheckSubsidised(CargoID cargo_type, CompanyID company, SourceType src_type, SourceID src, const Station *st)
00436 {
00437   /* If the source isn't subsidised, don't continue */
00438   if (src == INVALID_SOURCE) return false;
00439   switch (src_type) {
00440     case ST_INDUSTRY:
00441       if (!(Industry::Get(src)->part_of_subsidy & POS_SRC)) return false;
00442       break;
00443     case ST_TOWN:
00444       if (!(    Town::Get(src)->part_of_subsidy & POS_SRC)) return false;
00445       break;
00446     default: return false;
00447   }
00448 
00449   /* Remember all towns near this station (at least one house in its catchment radius)
00450    * which are destination of subsidised path. Do that only if needed */
00451   SmallVector<const Town *, 2> towns_near;
00452   if (!st->rect.IsEmpty()) {
00453     Subsidy *s;
00454     FOR_ALL_SUBSIDIES(s) {
00455       /* Don't create the cache if there is no applicable subsidy with town as destination */
00456       if (s->dst_type != ST_TOWN) continue;
00457       if (s->cargo_type != cargo_type || s->src_type != src_type || s->src != src) continue;
00458       if (s->IsAwarded() && s->awarded != company) continue;
00459 
00460       Rect rect = st->GetCatchmentRect();
00461 
00462       for (int y = rect.top; y <= rect.bottom; y++) {
00463         for (int x = rect.left; x <= rect.right; x++) {
00464           TileIndex tile = TileXY(x, y);
00465           if (!IsTileType(tile, MP_HOUSE)) continue;
00466           const Town *t = Town::GetByTile(tile);
00467           if (t->part_of_subsidy & POS_DST) towns_near.Include(t);
00468         }
00469       }
00470       break;
00471     }
00472   }
00473 
00474   bool subsidised = false;
00475 
00476   /* Check if there's a (new) subsidy that applies. There can be more subsidies triggered by this delivery!
00477    * Think about the case that subsidies are A->B and A->C and station has both B and C in its catchment area */
00478   Subsidy *s;
00479   FOR_ALL_SUBSIDIES(s) {
00480     if (s->cargo_type == cargo_type && s->src_type == src_type && s->src == src && (!s->IsAwarded() || s->awarded == company)) {
00481       switch (s->dst_type) {
00482         case ST_INDUSTRY:
00483           for (const Industry * const *ip = st->industries_near.Begin(); ip != st->industries_near.End(); ip++) {
00484             if (s->dst == (*ip)->index) {
00485               assert((*ip)->part_of_subsidy & POS_DST);
00486               subsidised = true;
00487               if (!s->IsAwarded()) s->AwardTo(company);
00488             }
00489           }
00490           break;
00491         case ST_TOWN:
00492           for (const Town * const *tp = towns_near.Begin(); tp != towns_near.End(); tp++) {
00493             if (s->dst == (*tp)->index) {
00494               assert((*tp)->part_of_subsidy & POS_DST);
00495               subsidised = true;
00496               if (!s->IsAwarded()) s->AwardTo(company);
00497             }
00498           }
00499           break;
00500         default:
00501           NOT_REACHED();
00502       }
00503     }
00504   }
00505 
00506   return subsidised;
00507 }