Fix 03cc0d6: Mark level crossings dirty when removing road from them, not from bridge...
[openttd-github.git] / src / subsidy.cpp
blob6b56feead20490f420ed38f793a9bb6f0056fe78
1 /*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
6 */
8 /** @file subsidy.cpp Handling of subsidies. */
10 #include "stdafx.h"
11 #include "company_func.h"
12 #include "industry.h"
13 #include "town.h"
14 #include "news_func.h"
15 #include "ai/ai.hpp"
16 #include "station_base.h"
17 #include "strings_func.h"
18 #include "window_func.h"
19 #include "subsidy_base.h"
20 #include "subsidy_func.h"
21 #include "core/pool_func.hpp"
22 #include "core/random_func.hpp"
23 #include "game/game.hpp"
24 #include "command_func.h"
25 #include "string_func.h"
26 #include "tile_cmd.h"
27 #include "subsidy_cmd.h"
29 #include "table/strings.h"
31 #include "safeguards.h"
33 SubsidyPool _subsidy_pool("Subsidy"); ///< Pool for the subsidies.
34 INSTANTIATE_POOL_METHODS(Subsidy)
36 /**
37 * Marks subsidy as awarded, creates news and AI event
38 * @param company awarded company
40 void Subsidy::AwardTo(CompanyID company)
42 assert(!this->IsAwarded());
44 this->awarded = company;
45 this->remaining = _settings_game.difficulty.subsidy_duration * MONTHS_IN_YEAR;
47 SetDParam(0, company);
48 NewsStringData *company_name = new NewsStringData(GetString(STR_COMPANY_NAME));
50 /* Add a news item */
51 std::pair<NewsReferenceType, NewsReferenceType> reftype = SetupSubsidyDecodeParam(this, SubsidyDecodeParamType::NewsAwarded, 1);
53 SetDParamStr(0, company_name->string);
54 AddNewsItem(
55 STR_NEWS_SERVICE_SUBSIDY_AWARDED_HALF + _settings_game.difficulty.subsidy_multiplier,
56 NT_SUBSIDIES, NF_NORMAL,
57 reftype.first, this->src, reftype.second, this->dst,
58 company_name
60 AI::BroadcastNewEvent(new ScriptEventSubsidyAwarded(this->index));
61 Game::NewEvent(new ScriptEventSubsidyAwarded(this->index));
63 InvalidateWindowData(WC_SUBSIDIES_LIST, 0);
66 /**
67 * Setup the string parameters for printing the subsidy at the screen, and compute the news reference for the subsidy.
68 * @param s %Subsidy being printed.
69 * @param mode Type of subsidy news message to decide on parameter format.
70 * @param parameter_offset The location/index in the String DParams to start decoding the subsidy's parameters. Defaults to 0.
71 * @return Reference of the subsidy in the news system.
73 std::pair<NewsReferenceType, NewsReferenceType> SetupSubsidyDecodeParam(const Subsidy *s, SubsidyDecodeParamType mode, uint parameter_offset)
75 NewsReferenceType reftype1 = NR_NONE;
76 NewsReferenceType reftype2 = NR_NONE;
78 /* Always use the plural form of the cargo name - trying to decide between plural or singular causes issues for translations */
79 const CargoSpec *cs = CargoSpec::Get(s->cargo_type);
80 SetDParam(parameter_offset, cs->name);
82 switch (s->src_type) {
83 case ST_INDUSTRY:
84 reftype1 = NR_INDUSTRY;
85 SetDParam(parameter_offset + 1, STR_INDUSTRY_NAME);
86 break;
87 case ST_TOWN:
88 reftype1 = NR_TOWN;
89 SetDParam(parameter_offset + 1, STR_TOWN_NAME);
90 break;
91 default: NOT_REACHED();
93 SetDParam(parameter_offset + 2, s->src);
95 switch (s->dst_type) {
96 case ST_INDUSTRY:
97 reftype2 = NR_INDUSTRY;
98 SetDParam(parameter_offset + 4, STR_INDUSTRY_NAME);
99 break;
100 case ST_TOWN:
101 reftype2 = NR_TOWN;
102 SetDParam(parameter_offset + 4, STR_TOWN_NAME);
103 break;
104 default: NOT_REACHED();
106 SetDParam(parameter_offset + 5, s->dst);
108 /* If the subsidy is being offered or awarded, the news item mentions the subsidy duration. */
109 if (mode == SubsidyDecodeParamType::NewsOffered || mode == SubsidyDecodeParamType::NewsAwarded) {
110 SetDParam(parameter_offset + 7, _settings_game.difficulty.subsidy_duration);
113 return std::pair<NewsReferenceType, NewsReferenceType>(reftype1, reftype2);
117 * Sets a flag indicating that given town/industry is part of subsidised route.
118 * @param type is it a town or an industry?
119 * @param index index of town/industry
120 * @param flag flag to set
122 static inline void SetPartOfSubsidyFlag(SourceType type, SourceID index, PartOfSubsidy flag)
124 switch (type) {
125 case ST_INDUSTRY: Industry::Get(index)->part_of_subsidy |= flag; return;
126 case ST_TOWN: Town::Get(index)->cache.part_of_subsidy |= flag; return;
127 default: NOT_REACHED();
131 /** Perform a full rebuild of the subsidies cache. */
132 void RebuildSubsidisedSourceAndDestinationCache()
134 for (Town *t : Town::Iterate()) t->cache.part_of_subsidy = POS_NONE;
136 for (Industry *i : Industry::Iterate()) i->part_of_subsidy = POS_NONE;
138 for (const Subsidy *s : Subsidy::Iterate()) {
139 SetPartOfSubsidyFlag(s->src_type, s->src, POS_SRC);
140 SetPartOfSubsidyFlag(s->dst_type, s->dst, POS_DST);
145 * Delete the subsidies associated with a given cargo source type and id.
146 * @param type Cargo source type of the id.
147 * @param index Id to remove.
149 void DeleteSubsidyWith(SourceType type, SourceID index)
151 bool dirty = false;
153 for (Subsidy *s : Subsidy::Iterate()) {
154 if ((s->src_type == type && s->src == index) || (s->dst_type == type && s->dst == index)) {
155 delete s;
156 dirty = true;
160 if (dirty) {
161 InvalidateWindowData(WC_SUBSIDIES_LIST, 0);
162 RebuildSubsidisedSourceAndDestinationCache();
167 * Check whether a specific subsidy already exists.
168 * @param cargo Cargo type.
169 * @param src_type Type of source of the cargo, affects interpretation of \a src.
170 * @param src Id of the source.
171 * @param dst_type Type of the destination of the cargo, affects interpretation of \a dst.
172 * @param dst Id of the destination.
173 * @return \c true if the subsidy already exists, \c false if not.
175 static bool CheckSubsidyDuplicate(CargoID cargo, SourceType src_type, SourceID src, SourceType dst_type, SourceID dst)
177 for (const Subsidy *s : Subsidy::Iterate()) {
178 if (s->cargo_type == cargo &&
179 s->src_type == src_type && s->src == src &&
180 s->dst_type == dst_type && s->dst == dst) {
181 return true;
184 return false;
188 * Checks if the source and destination of a subsidy are inside the distance limit.
189 * @param src_type Type of \a src.
190 * @param src Index of source.
191 * @param dst_type Type of \a dst.
192 * @param dst Index of destination.
193 * @return True if they are inside the distance limit.
195 static bool CheckSubsidyDistance(SourceType src_type, SourceID src, SourceType dst_type, SourceID dst)
197 TileIndex tile_src = (src_type == ST_TOWN) ? Town::Get(src)->xy : Industry::Get(src)->location.tile;
198 TileIndex tile_dst = (dst_type == ST_TOWN) ? Town::Get(dst)->xy : Industry::Get(dst)->location.tile;
200 return (DistanceManhattan(tile_src, tile_dst) <= SUBSIDY_MAX_DISTANCE);
204 * Creates a subsidy with the given parameters.
205 * @param cid Subsidised cargo.
206 * @param src_type Type of \a src.
207 * @param src Index of source.
208 * @param dst_type Type of \a dst.
209 * @param dst Index of destination.
211 void CreateSubsidy(CargoID cid, SourceType src_type, SourceID src, SourceType dst_type, SourceID dst)
213 Subsidy *s = new Subsidy();
214 s->cargo_type = cid;
215 s->src_type = src_type;
216 s->src = src;
217 s->dst_type = dst_type;
218 s->dst = dst;
219 s->remaining = SUBSIDY_OFFER_MONTHS;
220 s->awarded = INVALID_COMPANY;
222 std::pair<NewsReferenceType, NewsReferenceType> reftype = SetupSubsidyDecodeParam(s, SubsidyDecodeParamType::NewsOffered);
223 AddNewsItem(STR_NEWS_SERVICE_SUBSIDY_OFFERED, NT_SUBSIDIES, NF_NORMAL, reftype.first, s->src, reftype.second, s->dst);
224 SetPartOfSubsidyFlag(s->src_type, s->src, POS_SRC);
225 SetPartOfSubsidyFlag(s->dst_type, s->dst, POS_DST);
226 AI::BroadcastNewEvent(new ScriptEventSubsidyOffer(s->index));
227 Game::NewEvent(new ScriptEventSubsidyOffer(s->index));
229 InvalidateWindowData(WC_SUBSIDIES_LIST, 0);
233 * Create a new subsidy.
234 * @param flags type of operation
235 * @param cid CargoID of subsidy.
236 * @param src_type SourceType of source.
237 * @param src SourceID of source.
238 * @param dst_type SourceType of destination.
239 * @param dst SourceID of destination.
240 * @return the cost of this operation or an error
242 CommandCost CmdCreateSubsidy(DoCommandFlag flags, CargoID cid, SourceType src_type, SourceID src, SourceType dst_type, SourceID dst)
244 if (!Subsidy::CanAllocateItem()) return CMD_ERROR;
246 if (_current_company != OWNER_DEITY) return CMD_ERROR;
248 if (cid >= NUM_CARGO || !::CargoSpec::Get(cid)->IsValid()) return CMD_ERROR;
250 switch (src_type) {
251 case ST_TOWN:
252 if (!Town::IsValidID(src)) return CMD_ERROR;
253 break;
254 case ST_INDUSTRY:
255 if (!Industry::IsValidID(src)) return CMD_ERROR;
256 break;
257 default:
258 return CMD_ERROR;
260 switch (dst_type) {
261 case ST_TOWN:
262 if (!Town::IsValidID(dst)) return CMD_ERROR;
263 break;
264 case ST_INDUSTRY:
265 if (!Industry::IsValidID(dst)) return CMD_ERROR;
266 break;
267 default:
268 return CMD_ERROR;
271 if (flags & DC_EXEC) {
272 CreateSubsidy(cid, src_type, src, dst_type, dst);
275 return CommandCost();
279 * Tries to create a passenger subsidy between two towns.
280 * @return True iff the subsidy was created.
282 bool FindSubsidyPassengerRoute()
284 if (!Subsidy::CanAllocateItem()) return false;
286 const Town *src = Town::GetRandom();
287 if (src->cache.population < SUBSIDY_PAX_MIN_POPULATION ||
288 src->GetPercentTransported(CT_PASSENGERS) > SUBSIDY_MAX_PCT_TRANSPORTED) {
289 return false;
292 const Town *dst = Town::GetRandom();
293 if (dst->cache.population < SUBSIDY_PAX_MIN_POPULATION || src == dst) {
294 return false;
297 if (DistanceManhattan(src->xy, dst->xy) > SUBSIDY_MAX_DISTANCE) return false;
298 if (CheckSubsidyDuplicate(CT_PASSENGERS, ST_TOWN, src->index, ST_TOWN, dst->index)) return false;
300 CreateSubsidy(CT_PASSENGERS, ST_TOWN, src->index, ST_TOWN, dst->index);
302 return true;
305 bool FindSubsidyCargoDestination(CargoID cid, SourceType src_type, SourceID src);
309 * Tries to create a cargo subsidy with a town as source.
310 * @return True iff the subsidy was created.
312 bool FindSubsidyTownCargoRoute()
314 if (!Subsidy::CanAllocateItem()) return false;
316 SourceType src_type = ST_TOWN;
318 /* Select a random town. */
319 const Town *src_town = Town::GetRandom();
320 if (src_town->cache.population < SUBSIDY_CARGO_MIN_POPULATION) return false;
322 /* Calculate the produced cargo of houses around town center. */
323 CargoArray town_cargo_produced;
324 TileArea ta = TileArea(src_town->xy, 1, 1).Expand(SUBSIDY_TOWN_CARGO_RADIUS);
325 for (TileIndex tile : ta) {
326 if (IsTileType(tile, MP_HOUSE)) {
327 AddProducedCargo(tile, town_cargo_produced);
331 /* Passenger subsidies are not handled here. */
332 town_cargo_produced[CT_PASSENGERS] = 0;
334 uint8 cargo_count = 0;
335 for (CargoID i = 0; i < NUM_CARGO; i++) {
336 if (town_cargo_produced[i] > 0) cargo_count++;
339 /* No cargo produced at all? */
340 if (cargo_count == 0) return false;
342 /* Choose a random cargo that is produced in the town. */
343 uint8 cargo_number = RandomRange(cargo_count);
344 CargoID cid;
345 for (cid = 0; cid < NUM_CARGO; cid++) {
346 if (town_cargo_produced[cid] > 0) {
347 if (cargo_number == 0) break;
348 cargo_number--;
352 /* Avoid using invalid NewGRF cargoes. */
353 if (!CargoSpec::Get(cid)->IsValid() ||
354 _settings_game.linkgraph.GetDistributionType(cid) != DT_MANUAL) {
355 return false;
358 /* Quit if the percentage transported is large enough. */
359 if (src_town->GetPercentTransported(cid) > SUBSIDY_MAX_PCT_TRANSPORTED) return false;
361 SourceID src = src_town->index;
363 return FindSubsidyCargoDestination(cid, src_type, src);
367 * Tries to create a cargo subsidy with an industry as source.
368 * @return True iff the subsidy was created.
370 bool FindSubsidyIndustryCargoRoute()
372 if (!Subsidy::CanAllocateItem()) return false;
374 SourceType src_type = ST_INDUSTRY;
376 /* Select a random industry. */
377 const Industry *src_ind = Industry::GetRandom();
378 if (src_ind == nullptr) return false;
380 uint trans, total;
382 CargoID cid;
384 /* Randomize cargo type */
385 int num_cargos = 0;
386 uint cargo_index;
387 for (cargo_index = 0; cargo_index < lengthof(src_ind->produced_cargo); cargo_index++) {
388 if (src_ind->produced_cargo[cargo_index] != CT_INVALID) num_cargos++;
390 if (num_cargos == 0) return false; // industry produces nothing
391 int cargo_num = RandomRange(num_cargos) + 1;
392 for (cargo_index = 0; cargo_index < lengthof(src_ind->produced_cargo); cargo_index++) {
393 if (src_ind->produced_cargo[cargo_index] != CT_INVALID) cargo_num--;
394 if (cargo_num == 0) break;
396 assert(cargo_num == 0); // indicates loop didn't break as intended
397 cid = src_ind->produced_cargo[cargo_index];
398 trans = src_ind->last_month_pct_transported[cargo_index];
399 total = src_ind->last_month_production[cargo_index];
401 /* Quit if no production in this industry
402 * or if the pct transported is already large enough
403 * or if the cargo is automatically distributed */
404 if (total == 0 || trans > SUBSIDY_MAX_PCT_TRANSPORTED ||
405 cid == CT_INVALID ||
406 _settings_game.linkgraph.GetDistributionType(cid) != DT_MANUAL) {
407 return false;
410 SourceID src = src_ind->index;
412 return FindSubsidyCargoDestination(cid, src_type, src);
416 * Tries to find a suitable destination for the given source and cargo.
417 * @param cid Subsidized cargo.
418 * @param src_type Type of \a src.
419 * @param src Index of source.
420 * @return True iff the subsidy was created.
422 bool FindSubsidyCargoDestination(CargoID cid, SourceType src_type, SourceID src)
424 /* Choose a random destination. */
425 SourceType dst_type = Chance16(1, 2) ? ST_TOWN : ST_INDUSTRY;
427 SourceID dst;
428 switch (dst_type) {
429 case ST_TOWN: {
430 /* Select a random town. */
431 const Town *dst_town = Town::GetRandom();
433 /* Calculate cargo acceptance of houses around town center. */
434 CargoArray town_cargo_accepted;
435 TileArea ta = TileArea(dst_town->xy, 1, 1).Expand(SUBSIDY_TOWN_CARGO_RADIUS);
436 for (TileIndex tile : ta) {
437 if (IsTileType(tile, MP_HOUSE)) {
438 AddAcceptedCargo(tile, town_cargo_accepted, nullptr);
442 /* Check if the town can accept this cargo. */
443 if (town_cargo_accepted[cid] < 8) return false;
445 dst = dst_town->index;
446 break;
449 case ST_INDUSTRY: {
450 /* Select a random industry. */
451 const Industry *dst_ind = Industry::GetRandom();
452 if (dst_ind == nullptr) return false;
454 /* The industry must accept the cargo */
455 bool valid = std::find(dst_ind->accepts_cargo, endof(dst_ind->accepts_cargo), cid) != endof(dst_ind->accepts_cargo);
456 if (!valid) return false;
458 dst = dst_ind->index;
459 break;
462 default: NOT_REACHED();
465 /* Check that the source and the destination are not the same. */
466 if (src_type == dst_type && src == dst) return false;
468 /* Check distance between source and destination. */
469 if (!CheckSubsidyDistance(src_type, src, dst_type, dst)) return false;
471 /* Avoid duplicate subsidies. */
472 if (CheckSubsidyDuplicate(cid, src_type, src, dst_type, dst)) return false;
474 CreateSubsidy(cid, src_type, src, dst_type, dst);
476 return true;
479 /** Perform the monthly update of open subsidies, and try to create a new one. */
480 void SubsidyMonthlyLoop()
482 bool modified = false;
484 for (Subsidy *s : Subsidy::Iterate()) {
485 if (--s->remaining == 0) {
486 if (!s->IsAwarded()) {
487 std::pair<NewsReferenceType, NewsReferenceType> reftype = SetupSubsidyDecodeParam(s, SubsidyDecodeParamType::NewsWithdrawn);
488 AddNewsItem(STR_NEWS_OFFER_OF_SUBSIDY_EXPIRED, NT_SUBSIDIES, NF_NORMAL, reftype.first, s->src, reftype.second, s->dst);
489 AI::BroadcastNewEvent(new ScriptEventSubsidyOfferExpired(s->index));
490 Game::NewEvent(new ScriptEventSubsidyOfferExpired(s->index));
491 } else {
492 if (s->awarded == _local_company) {
493 std::pair<NewsReferenceType, NewsReferenceType> reftype = SetupSubsidyDecodeParam(s, SubsidyDecodeParamType::NewsWithdrawn);
494 AddNewsItem(STR_NEWS_SUBSIDY_WITHDRAWN_SERVICE, NT_SUBSIDIES, NF_NORMAL, reftype.first, s->src, reftype.second, s->dst);
496 AI::BroadcastNewEvent(new ScriptEventSubsidyExpired(s->index));
497 Game::NewEvent(new ScriptEventSubsidyExpired(s->index));
499 delete s;
500 modified = true;
504 if (modified) {
505 RebuildSubsidisedSourceAndDestinationCache();
506 } else if (_settings_game.difficulty.subsidy_duration == 0) {
507 /* If subsidy duration is set to 0, subsidies are disabled, so bail out. */
508 return;
509 } else if (_settings_game.linkgraph.distribution_pax != DT_MANUAL &&
510 _settings_game.linkgraph.distribution_mail != DT_MANUAL &&
511 _settings_game.linkgraph.distribution_armoured != DT_MANUAL &&
512 _settings_game.linkgraph.distribution_default != DT_MANUAL) {
513 /* Return early if there are no manually distributed cargoes and if we
514 * don't need to invalidate the subsidies window. */
515 return;
518 bool passenger_subsidy = false;
519 bool town_subsidy = false;
520 bool industry_subsidy = false;
522 int random_chance = RandomRange(16);
524 if (random_chance < 2 && _settings_game.linkgraph.distribution_pax == DT_MANUAL) {
525 /* There is a 1/8 chance each month of generating a passenger subsidy. */
526 int n = 1000;
528 do {
529 passenger_subsidy = FindSubsidyPassengerRoute();
530 } while (!passenger_subsidy && n--);
531 } else if (random_chance == 2) {
532 /* Cargo subsidies with a town as a source have a 1/16 chance. */
533 int n = 1000;
535 do {
536 town_subsidy = FindSubsidyTownCargoRoute();
537 } while (!town_subsidy && n--);
538 } else if (random_chance == 3) {
539 /* Cargo subsidies with an industry as a source have a 1/16 chance. */
540 int n = 1000;
542 do {
543 industry_subsidy = FindSubsidyIndustryCargoRoute();
544 } while (!industry_subsidy && n--);
547 modified |= passenger_subsidy || town_subsidy || industry_subsidy;
549 if (modified) InvalidateWindowData(WC_SUBSIDIES_LIST, 0);
553 * Tests whether given delivery is subsidised and possibly awards the subsidy to delivering company
554 * @param cargo_type type of cargo
555 * @param company company delivering the cargo
556 * @param src_type type of \a src
557 * @param src index of source
558 * @param st station where the cargo is delivered to
559 * @return is the delivery subsidised?
561 bool CheckSubsidised(CargoID cargo_type, CompanyID company, SourceType src_type, SourceID src, const Station *st)
563 /* If the source isn't subsidised, don't continue */
564 if (src == INVALID_SOURCE) return false;
565 switch (src_type) {
566 case ST_INDUSTRY:
567 if (!(Industry::Get(src)->part_of_subsidy & POS_SRC)) return false;
568 break;
569 case ST_TOWN:
570 if (!(Town::Get(src)->cache.part_of_subsidy & POS_SRC)) return false;
571 break;
572 default: return false;
575 /* Remember all towns near this station (at least one house in its catchment radius)
576 * which are destination of subsidised path. Do that only if needed */
577 std::vector<const Town *> towns_near;
578 if (!st->rect.IsEmpty()) {
579 for (const Subsidy *s : Subsidy::Iterate()) {
580 /* Don't create the cache if there is no applicable subsidy with town as destination */
581 if (s->dst_type != ST_TOWN) continue;
582 if (s->cargo_type != cargo_type || s->src_type != src_type || s->src != src) continue;
583 if (s->IsAwarded() && s->awarded != company) continue;
585 BitmapTileIterator it(st->catchment_tiles);
586 for (TileIndex tile = it; tile != INVALID_TILE; tile = ++it) {
587 if (!IsTileType(tile, MP_HOUSE)) continue;
588 const Town *t = Town::GetByTile(tile);
589 if (t->cache.part_of_subsidy & POS_DST) include(towns_near, t);
591 break;
595 bool subsidised = false;
597 /* Check if there's a (new) subsidy that applies. There can be more subsidies triggered by this delivery!
598 * Think about the case that subsidies are A->B and A->C and station has both B and C in its catchment area */
599 for (Subsidy *s : Subsidy::Iterate()) {
600 if (s->cargo_type == cargo_type && s->src_type == src_type && s->src == src && (!s->IsAwarded() || s->awarded == company)) {
601 switch (s->dst_type) {
602 case ST_INDUSTRY:
603 for (const auto &i : st->industries_near) {
604 if (s->dst == i.industry->index) {
605 assert(i.industry->part_of_subsidy & POS_DST);
606 subsidised = true;
607 if (!s->IsAwarded()) s->AwardTo(company);
610 break;
611 case ST_TOWN:
612 for (const Town *tp : towns_near) {
613 if (s->dst == tp->index) {
614 assert(tp->cache.part_of_subsidy & POS_DST);
615 subsidised = true;
616 if (!s->IsAwarded()) s->AwardTo(company);
619 break;
620 default:
621 NOT_REACHED();
626 return subsidised;