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 #include "game/game.hpp"
00027 #include "command_func.h"
00028 
00029 #include "table/strings.h"
00030 
00031 SubsidyPool _subsidy_pool("Subsidy"); 
00032 INSTANTIATE_POOL_METHODS(Subsidy)
00033 
00034 
00038 void Subsidy::AwardTo(CompanyID company)
00039 {
00040   assert(!this->IsAwarded());
00041 
00042   this->awarded = company;
00043   this->remaining = SUBSIDY_CONTRACT_MONTHS;
00044 
00045   char company_name[MAX_LENGTH_COMPANY_NAME_CHARS * MAX_CHAR_LENGTH];
00046   SetDParam(0, company);
00047   GetString(company_name, STR_COMPANY_NAME, lastof(company_name));
00048 
00049   char *cn = strdup(company_name);
00050 
00051   /* Add a news item */
00052   Pair reftype = SetupSubsidyDecodeParam(this, false);
00053   InjectDParam(1);
00054 
00055   SetDParamStr(0, cn);
00056   AddNewsItem(
00057     STR_NEWS_SERVICE_SUBSIDY_AWARDED_HALF + _settings_game.difficulty.subsidy_multiplier,
00058     NS_SUBSIDIES,
00059     (NewsReferenceType)reftype.a, this->src, (NewsReferenceType)reftype.b, this->dst,
00060     cn
00061   );
00062   AI::BroadcastNewEvent(new ScriptEventSubsidyAwarded(this->index));
00063   Game::NewEvent(new ScriptEventSubsidyAwarded(this->index));
00064 
00065   InvalidateWindowData(WC_SUBSIDIES_LIST, 0);
00066 }
00067 
00074 Pair SetupSubsidyDecodeParam(const Subsidy *s, bool mode)
00075 {
00076   NewsReferenceType reftype1 = NR_NONE;
00077   NewsReferenceType reftype2 = NR_NONE;
00078 
00079   /* if mode is false, use the singular form */
00080   const CargoSpec *cs = CargoSpec::Get(s->cargo_type);
00081   SetDParam(0, mode ? cs->name : cs->name_single);
00082 
00083   switch (s->src_type) {
00084     case ST_INDUSTRY:
00085       reftype1 = NR_INDUSTRY;
00086       SetDParam(1, STR_INDUSTRY_NAME);
00087       break;
00088     case ST_TOWN:
00089       reftype1 = NR_TOWN;
00090       SetDParam(1, STR_TOWN_NAME);
00091       break;
00092     default: NOT_REACHED();
00093   }
00094   SetDParam(2, s->src);
00095 
00096   switch (s->dst_type) {
00097     case ST_INDUSTRY:
00098       reftype2 = NR_INDUSTRY;
00099       SetDParam(4, STR_INDUSTRY_NAME);
00100       break;
00101     case ST_TOWN:
00102       reftype2 = NR_TOWN;
00103       SetDParam(4, STR_TOWN_NAME);
00104       break;
00105     default: NOT_REACHED();
00106   }
00107   SetDParam(5, s->dst);
00108 
00109   Pair p;
00110   p.a = reftype1;
00111   p.b = reftype2;
00112   return p;
00113 }
00114 
00121 static inline void SetPartOfSubsidyFlag(SourceType type, SourceID index, PartOfSubsidy flag)
00122 {
00123   switch (type) {
00124     case ST_INDUSTRY: Industry::Get(index)->part_of_subsidy |= flag; return;
00125     case ST_TOWN:         Town::Get(index)->part_of_subsidy |= flag; return;
00126     default: NOT_REACHED();
00127   }
00128 }
00129 
00131 void RebuildSubsidisedSourceAndDestinationCache()
00132 {
00133   Town *t;
00134   FOR_ALL_TOWNS(t) t->part_of_subsidy = POS_NONE;
00135 
00136   Industry *i;
00137   FOR_ALL_INDUSTRIES(i) i->part_of_subsidy = POS_NONE;
00138 
00139   const Subsidy *s;
00140   FOR_ALL_SUBSIDIES(s) {
00141     SetPartOfSubsidyFlag(s->src_type, s->src, POS_SRC);
00142     SetPartOfSubsidyFlag(s->dst_type, s->dst, POS_DST);
00143   }
00144 }
00145 
00151 void DeleteSubsidyWith(SourceType type, SourceID index)
00152 {
00153   bool dirty = false;
00154 
00155   Subsidy *s;
00156   FOR_ALL_SUBSIDIES(s) {
00157     if ((s->src_type == type && s->src == index) || (s->dst_type == type && s->dst == index)) {
00158       delete s;
00159       dirty = true;
00160     }
00161   }
00162 
00163   if (dirty) {
00164     InvalidateWindowData(WC_SUBSIDIES_LIST, 0);
00165     RebuildSubsidisedSourceAndDestinationCache();
00166   }
00167 }
00168 
00178 static bool CheckSubsidyDuplicate(CargoID cargo, SourceType src_type, SourceID src, SourceType dst_type, SourceID dst)
00179 {
00180   const Subsidy *s;
00181   FOR_ALL_SUBSIDIES(s) {
00182     if (s->cargo_type == cargo &&
00183         s->src_type == src_type && s->src == src &&
00184         s->dst_type == dst_type && s->dst == dst) {
00185       return true;
00186     }
00187   }
00188   return false;
00189 }
00190 
00199 static bool CheckSubsidyDistance(SourceType src_type, SourceID src, SourceType dst_type, SourceID dst)
00200 {
00201   TileIndex tile_src = (src_type == ST_TOWN) ? Town::Get(src)->xy : Industry::Get(src)->location.tile;
00202   TileIndex tile_dst = (dst_type == ST_TOWN) ? Town::Get(dst)->xy : Industry::Get(dst)->location.tile;
00203 
00204   return (DistanceManhattan(tile_src, tile_dst) <= SUBSIDY_MAX_DISTANCE);
00205 }
00206 
00215 void CreateSubsidy(CargoID cid, SourceType src_type, SourceID src, SourceType dst_type, SourceID dst)
00216 {
00217   Subsidy *s = new Subsidy();
00218   s->cargo_type = cid;
00219   s->src_type = src_type;
00220   s->src = src;
00221   s->dst_type = dst_type;
00222   s->dst = dst;
00223   s->remaining = SUBSIDY_OFFER_MONTHS;
00224   s->awarded = INVALID_COMPANY;
00225 
00226   Pair reftype = SetupSubsidyDecodeParam(s, false);
00227   AddNewsItem(STR_NEWS_SERVICE_SUBSIDY_OFFERED, NS_SUBSIDIES, (NewsReferenceType)reftype.a, s->src, (NewsReferenceType)reftype.b, s->dst);
00228   SetPartOfSubsidyFlag(s->src_type, s->src, POS_SRC);
00229   SetPartOfSubsidyFlag(s->dst_type, s->dst, POS_DST);
00230   AI::BroadcastNewEvent(new ScriptEventSubsidyOffer(s->index));
00231   Game::NewEvent(new ScriptEventSubsidyOffer(s->index));
00232 
00233   InvalidateWindowData(WC_SUBSIDIES_LIST, 0);
00234 }
00235 
00250 CommandCost CmdCreateSubsidy(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text)
00251 {
00252   if (!Subsidy::CanAllocateItem()) return CMD_ERROR;
00253 
00254   CargoID cid = GB(p1, 24, 8);
00255   SourceType src_type = (SourceType)GB(p1, 0, 8);
00256   SourceID src = GB(p1, 8, 16);
00257   SourceType dst_type = (SourceType)GB(p2, 0, 8);
00258   SourceID dst = GB(p2, 8, 16);
00259 
00260   if (_current_company != OWNER_DEITY) return CMD_ERROR;
00261 
00262   if (cid >= NUM_CARGO || !::CargoSpec::Get(cid)->IsValid()) return CMD_ERROR;
00263 
00264   switch (src_type) {
00265     case ST_TOWN:
00266       if (!Town::IsValidID(src)) return CMD_ERROR;
00267       break;
00268     case ST_INDUSTRY:
00269       if (!Industry::IsValidID(src)) return CMD_ERROR;
00270       break;
00271     default:
00272       return CMD_ERROR;
00273   }
00274   switch (dst_type) {
00275     case ST_TOWN:
00276       if (!Town::IsValidID(dst)) return CMD_ERROR;
00277       break;
00278     case ST_INDUSTRY:
00279       if (!Industry::IsValidID(dst)) return CMD_ERROR;
00280       break;
00281     default:
00282       return CMD_ERROR;
00283   }
00284 
00285   if (flags & DC_EXEC) {
00286     CreateSubsidy(cid, src_type, src, dst_type, dst);
00287   }
00288 
00289   return CommandCost();
00290 }
00291 
00296 bool FindSubsidyPassengerRoute()
00297 {
00298   if (!Subsidy::CanAllocateItem()) return false;
00299 
00300   const Town *src = Town::GetRandom();
00301   if (src->population < SUBSIDY_PAX_MIN_POPULATION ||
00302       src->GetPercentTransported(CT_PASSENGERS) > SUBSIDY_MAX_PCT_TRANSPORTED) {
00303     return false;
00304   }
00305 
00306   const Town *dst = Town::GetRandom();
00307   if (dst->population < SUBSIDY_PAX_MIN_POPULATION || src == dst) {
00308     return false;
00309   }
00310 
00311   if (DistanceManhattan(src->xy, dst->xy) > SUBSIDY_MAX_DISTANCE) return false;
00312   if (CheckSubsidyDuplicate(CT_PASSENGERS, ST_TOWN, src->index, ST_TOWN, dst->index)) return false;
00313 
00314   CreateSubsidy(CT_PASSENGERS, ST_TOWN, src->index, ST_TOWN, dst->index);
00315 
00316   return true;
00317 }
00318 
00319 bool FindSubsidyCargoDestination(CargoID cid, SourceType src_type, SourceID src);
00320 
00321 
00326 bool FindSubsidyTownCargoRoute()
00327 {
00328   if (!Subsidy::CanAllocateItem()) return false;
00329 
00330   SourceType src_type = ST_TOWN;
00331 
00332   /* Select a random town. */
00333   const Town *src_town = Town::GetRandom();
00334 
00335   uint32 town_cargo_produced = src_town->cargo_produced;
00336 
00337   /* Passenger subsidies are not handled here. */
00338   ClrBit(town_cargo_produced, CT_PASSENGERS);
00339 
00340   /* Choose a random cargo that is produced in the town. */
00341   uint8 cargo_number = RandomRange(CountBits(town_cargo_produced));
00342   CargoID cid;
00343   FOR_EACH_SET_CARGO_ID(cid, town_cargo_produced) {
00344     if (cargo_number == 0) break;
00345     cargo_number--;
00346   }
00347 
00348   /* Avoid using invalid NewGRF cargoes. */
00349   if (!CargoSpec::Get(cid)->IsValid()) return false;
00350 
00351   /* Quit if the percentage transported is large enough. */
00352   if (src_town->GetPercentTransported(cid) > SUBSIDY_MAX_PCT_TRANSPORTED) return false;
00353 
00354   SourceID src = src_town->index;
00355 
00356   return FindSubsidyCargoDestination(cid, src_type, src);
00357 }
00358 
00363 bool FindSubsidyIndustryCargoRoute()
00364 {
00365   if (!Subsidy::CanAllocateItem()) return false;
00366 
00367   SourceType src_type = ST_INDUSTRY;
00368 
00369   /* Select a random industry. */
00370   const Industry *src_ind = Industry::GetRandom();
00371   if (src_ind == NULL) return false;
00372 
00373   uint trans, total;
00374 
00375   CargoID cid;
00376 
00377   /* Randomize cargo type */
00378   if (src_ind->produced_cargo[1] != CT_INVALID && HasBit(Random(), 0)) {
00379     cid = src_ind->produced_cargo[1];
00380     trans = src_ind->last_month_pct_transported[1];
00381     total = src_ind->last_month_production[1];
00382   } else {
00383     cid = src_ind->produced_cargo[0];
00384     trans = src_ind->last_month_pct_transported[0];
00385     total = src_ind->last_month_production[0];
00386   }
00387 
00388   /* Quit if no production in this industry
00389    * or if the pct transported is already large enough */
00390   if (total == 0 || trans > SUBSIDY_MAX_PCT_TRANSPORTED || cid == CT_INVALID) return false;
00391 
00392   SourceID src = src_ind->index;
00393 
00394   return FindSubsidyCargoDestination(cid, src_type, src);
00395 }
00396 
00404 bool FindSubsidyCargoDestination(CargoID cid, SourceType src_type, SourceID src)
00405 {
00406   /* Choose a random destination. Only consider towns if they can accept the cargo. */
00407   SourceType dst_type = (HasBit(_town_cargoes_accepted, cid) && Chance16(1, 2)) ? ST_TOWN : ST_INDUSTRY;
00408 
00409   SourceID dst;
00410   switch (dst_type) {
00411     case ST_TOWN: {
00412       /* Select a random town. */
00413       const Town *dst_town = Town::GetRandom();
00414 
00415       /* Check if the town can accept this cargo. */
00416       if (!HasBit(dst_town->cargo_accepted_total, cid)) return false;
00417 
00418       dst = dst_town->index;
00419       break;
00420     }
00421 
00422     case ST_INDUSTRY: {
00423       /* Select a random industry. */
00424       const Industry *dst_ind = Industry::GetRandom();
00425 
00426       /* The industry must accept the cargo */
00427       if (dst_ind == NULL ||
00428           (cid != dst_ind->accepts_cargo[0] &&
00429            cid != dst_ind->accepts_cargo[1] &&
00430            cid != dst_ind->accepts_cargo[2])) {
00431         return false;
00432       }
00433 
00434       dst = dst_ind->index;
00435       break;
00436     }
00437 
00438     default: NOT_REACHED();
00439   }
00440 
00441   /* Check that the source and the destination are not the same. */
00442   if (src_type == dst_type && src == dst) return false;
00443 
00444   /* Check distance between source and destination. */
00445   if (!CheckSubsidyDistance(src_type, src, dst_type, dst)) return false;
00446 
00447   /* Avoid duplicate subsidies. */
00448   if (CheckSubsidyDuplicate(cid, src_type, src, dst_type, dst)) return false;
00449 
00450   CreateSubsidy(cid, src_type, src, dst_type, dst);
00451 
00452   return true;
00453 }
00454 
00456 void SubsidyMonthlyLoop()
00457 {
00458   bool modified = false;
00459 
00460   Subsidy *s;
00461   FOR_ALL_SUBSIDIES(s) {
00462     if (--s->remaining == 0) {
00463       if (!s->IsAwarded()) {
00464         Pair reftype = SetupSubsidyDecodeParam(s, true);
00465         AddNewsItem(STR_NEWS_OFFER_OF_SUBSIDY_EXPIRED, NS_SUBSIDIES, (NewsReferenceType)reftype.a, s->src, (NewsReferenceType)reftype.b, s->dst);
00466         AI::BroadcastNewEvent(new ScriptEventSubsidyOfferExpired(s->index));
00467         Game::NewEvent(new ScriptEventSubsidyOfferExpired(s->index));
00468       } else {
00469         if (s->awarded == _local_company) {
00470           Pair reftype = SetupSubsidyDecodeParam(s, true);
00471           AddNewsItem(STR_NEWS_SUBSIDY_WITHDRAWN_SERVICE, NS_SUBSIDIES, (NewsReferenceType)reftype.a, s->src, (NewsReferenceType)reftype.b, s->dst);
00472         }
00473         AI::BroadcastNewEvent(new ScriptEventSubsidyExpired(s->index));
00474         Game::NewEvent(new ScriptEventSubsidyExpired(s->index));
00475       }
00476       delete s;
00477       modified = true;
00478     }
00479   }
00480 
00481   if (modified) RebuildSubsidisedSourceAndDestinationCache();
00482 
00483   bool passenger_subsidy = false;
00484   bool town_subsidy = false;
00485   bool industry_subsidy = false;
00486 
00487   int random_chance = RandomRange(16);
00488 
00489   if (random_chance < 2) {
00490     /* There is a 1/8 chance each month of generating a passenger subsidy. */
00491     int n = 1000;
00492 
00493     do {
00494       passenger_subsidy = FindSubsidyPassengerRoute();
00495     } while (!passenger_subsidy && n--);
00496   } else if (random_chance == 2) {
00497     /* Cargo subsidies with a town as a source have a 1/16 chance. */
00498     int n = 1000;
00499 
00500     do {
00501       town_subsidy = FindSubsidyTownCargoRoute();
00502     } while (!town_subsidy && n--);
00503   } else if (random_chance == 3) {
00504     /* Cargo subsidies with an industry as a source have a 1/16 chance. */
00505     int n = 1000;
00506 
00507     do {
00508       industry_subsidy = FindSubsidyTownCargoRoute();
00509     } while (!industry_subsidy && n--);
00510   }
00511 
00512   modified |= passenger_subsidy || town_subsidy || industry_subsidy;
00513 
00514   if (modified) InvalidateWindowData(WC_SUBSIDIES_LIST, 0);
00515 }
00516 
00526 bool CheckSubsidised(CargoID cargo_type, CompanyID company, SourceType src_type, SourceID src, const Station *st)
00527 {
00528   /* If the source isn't subsidised, don't continue */
00529   if (src == INVALID_SOURCE) return false;
00530   switch (src_type) {
00531     case ST_INDUSTRY:
00532       if (!(Industry::Get(src)->part_of_subsidy & POS_SRC)) return false;
00533       break;
00534     case ST_TOWN:
00535       if (!(    Town::Get(src)->part_of_subsidy & POS_SRC)) return false;
00536       break;
00537     default: return false;
00538   }
00539 
00540   /* Remember all towns near this station (at least one house in its catchment radius)
00541    * which are destination of subsidised path. Do that only if needed */
00542   SmallVector<const Town *, 2> towns_near;
00543   if (!st->rect.IsEmpty()) {
00544     Subsidy *s;
00545     FOR_ALL_SUBSIDIES(s) {
00546       /* Don't create the cache if there is no applicable subsidy with town as destination */
00547       if (s->dst_type != ST_TOWN) continue;
00548       if (s->cargo_type != cargo_type || s->src_type != src_type || s->src != src) continue;
00549       if (s->IsAwarded() && s->awarded != company) continue;
00550 
00551       Rect rect = st->GetCatchmentRect();
00552 
00553       for (int y = rect.top; y <= rect.bottom; y++) {
00554         for (int x = rect.left; x <= rect.right; x++) {
00555           TileIndex tile = TileXY(x, y);
00556           if (!IsTileType(tile, MP_HOUSE)) continue;
00557           const Town *t = Town::GetByTile(tile);
00558           if (t->part_of_subsidy & POS_DST) towns_near.Include(t);
00559         }
00560       }
00561       break;
00562     }
00563   }
00564 
00565   bool subsidised = false;
00566 
00567   /* Check if there's a (new) subsidy that applies. There can be more subsidies triggered by this delivery!
00568    * Think about the case that subsidies are A->B and A->C and station has both B and C in its catchment area */
00569   Subsidy *s;
00570   FOR_ALL_SUBSIDIES(s) {
00571     if (s->cargo_type == cargo_type && s->src_type == src_type && s->src == src && (!s->IsAwarded() || s->awarded == company)) {
00572       switch (s->dst_type) {
00573         case ST_INDUSTRY:
00574           for (const Industry * const *ip = st->industries_near.Begin(); ip != st->industries_near.End(); ip++) {
00575             if (s->dst == (*ip)->index) {
00576               assert((*ip)->part_of_subsidy & POS_DST);
00577               subsidised = true;
00578               if (!s->IsAwarded()) s->AwardTo(company);
00579             }
00580           }
00581           break;
00582         case ST_TOWN:
00583           for (const Town * const *tp = towns_near.Begin(); tp != towns_near.End(); tp++) {
00584             if (s->dst == (*tp)->index) {
00585               assert((*tp)->part_of_subsidy & POS_DST);
00586               subsidised = true;
00587               if (!s->IsAwarded()) s->AwardTo(company);
00588             }
00589           }
00590           break;
00591         default:
00592           NOT_REACHED();
00593       }
00594     }
00595   }
00596 
00597   return subsidised;
00598 }