subsidy.cpp

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