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 */
7
8 /** @file script_town.cpp Implementation of ScriptTown. */
9
10 #include "../../stdafx.h"
11 #include "script_town.hpp"
12 #include "script_map.hpp"
13 #include "script_error.hpp"
14 #include "../../town.h"
15 #include "../../townname_func.h"
16 #include "../../string_func.h"
17 #include "../../strings_func.h"
18 #include "../../station_base.h"
19 #include "../../landscape.h"
20 #include "table/strings.h"
21
22 #include "../../safeguards.h"
23
GetTownCount()24 /* static */ int32 ScriptTown::GetTownCount()
25 {
26 return (int32)::Town::GetNumItems();
27 }
28
IsValidTown(TownID town_id)29 /* static */ bool ScriptTown::IsValidTown(TownID town_id)
30 {
31 return ::Town::IsValidID(town_id);
32 }
33
GetName(TownID town_id)34 /* static */ char *ScriptTown::GetName(TownID town_id)
35 {
36 if (!IsValidTown(town_id)) return nullptr;
37
38 ::SetDParam(0, town_id);
39 return GetString(STR_TOWN_NAME);
40 }
41
SetName(TownID town_id,Text * name)42 /* static */ bool ScriptTown::SetName(TownID town_id, Text *name)
43 {
44 CCountedPtr<Text> counter(name);
45
46 const char *text = nullptr;
47 if (name != nullptr) {
48 text = name->GetDecodedText();
49 EnforcePreconditionEncodedText(false, text);
50 EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_TOWN_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG);
51 }
52 EnforcePrecondition(false, IsValidTown(town_id));
53
54 return ScriptObject::DoCommand(0, town_id, 0, CMD_RENAME_TOWN, text);
55 }
56
SetText(TownID town_id,Text * text)57 /* static */ bool ScriptTown::SetText(TownID town_id, Text *text)
58 {
59 CCountedPtr<Text> counter(text);
60
61 const char *encoded_text = nullptr;
62 if (text != nullptr) {
63 encoded_text = text->GetEncodedText();
64 EnforcePreconditionEncodedText(false, encoded_text);
65 }
66 EnforcePrecondition(false, IsValidTown(town_id));
67
68 return ScriptObject::DoCommand(::Town::Get(town_id)->xy, town_id, 0, CMD_TOWN_SET_TEXT, encoded_text);
69 }
70
GetPopulation(TownID town_id)71 /* static */ int32 ScriptTown::GetPopulation(TownID town_id)
72 {
73 if (!IsValidTown(town_id)) return -1;
74 const Town *t = ::Town::Get(town_id);
75 return t->cache.population;
76 }
77
GetHouseCount(TownID town_id)78 /* static */ int32 ScriptTown::GetHouseCount(TownID town_id)
79 {
80 if (!IsValidTown(town_id)) return -1;
81 const Town *t = ::Town::Get(town_id);
82 return t->cache.num_houses;
83 }
84
GetLocation(TownID town_id)85 /* static */ TileIndex ScriptTown::GetLocation(TownID town_id)
86 {
87 if (!IsValidTown(town_id)) return INVALID_TILE;
88 const Town *t = ::Town::Get(town_id);
89 return t->xy;
90 }
91
GetLastMonthProduction(TownID town_id,CargoID cargo_id)92 /* static */ int32 ScriptTown::GetLastMonthProduction(TownID town_id, CargoID cargo_id)
93 {
94 if (!IsValidTown(town_id)) return -1;
95 if (!ScriptCargo::IsValidCargo(cargo_id)) return -1;
96
97 const Town *t = ::Town::Get(town_id);
98
99 return t->supplied[cargo_id].old_max;
100 }
101
GetLastMonthSupplied(TownID town_id,CargoID cargo_id)102 /* static */ int32 ScriptTown::GetLastMonthSupplied(TownID town_id, CargoID cargo_id)
103 {
104 if (!IsValidTown(town_id)) return -1;
105 if (!ScriptCargo::IsValidCargo(cargo_id)) return -1;
106
107 const Town *t = ::Town::Get(town_id);
108
109 return t->supplied[cargo_id].old_act;
110 }
111
GetLastMonthTransportedPercentage(TownID town_id,CargoID cargo_id)112 /* static */ int32 ScriptTown::GetLastMonthTransportedPercentage(TownID town_id, CargoID cargo_id)
113 {
114 if (!IsValidTown(town_id)) return -1;
115 if (!ScriptCargo::IsValidCargo(cargo_id)) return -1;
116
117 const Town *t = ::Town::Get(town_id);
118 return ::ToPercent8(t->GetPercentTransported(cargo_id));
119 }
120
GetLastMonthReceived(TownID town_id,ScriptCargo::TownEffect towneffect_id)121 /* static */ int32 ScriptTown::GetLastMonthReceived(TownID town_id, ScriptCargo::TownEffect towneffect_id)
122 {
123 if (!IsValidTown(town_id)) return -1;
124 if (!ScriptCargo::IsValidTownEffect(towneffect_id)) return -1;
125
126 const Town *t = ::Town::Get(town_id);
127
128 return t->received[towneffect_id].old_act;
129 }
130
SetCargoGoal(TownID town_id,ScriptCargo::TownEffect towneffect_id,uint32 goal)131 /* static */ bool ScriptTown::SetCargoGoal(TownID town_id, ScriptCargo::TownEffect towneffect_id, uint32 goal)
132 {
133 EnforcePrecondition(false, IsValidTown(town_id));
134 EnforcePrecondition(false, ScriptCargo::IsValidTownEffect(towneffect_id));
135
136 return ScriptObject::DoCommand(::Town::Get(town_id)->xy, town_id | (towneffect_id << 16), goal, CMD_TOWN_CARGO_GOAL);
137 }
138
GetCargoGoal(TownID town_id,ScriptCargo::TownEffect towneffect_id)139 /* static */ uint32 ScriptTown::GetCargoGoal(TownID town_id, ScriptCargo::TownEffect towneffect_id)
140 {
141 if (!IsValidTown(town_id)) return UINT32_MAX;
142 if (!ScriptCargo::IsValidTownEffect(towneffect_id)) return UINT32_MAX;
143
144 const Town *t = ::Town::Get(town_id);
145
146 switch (t->goal[towneffect_id]) {
147 case TOWN_GROWTH_WINTER:
148 if (TileHeight(t->xy) >= GetSnowLine() && t->cache.population > 90) return 1;
149 return 0;
150
151 case TOWN_GROWTH_DESERT:
152 if (GetTropicZone(t->xy) == TROPICZONE_DESERT && t->cache.population > 60) return 1;
153 return 0;
154
155 default: return t->goal[towneffect_id];
156 }
157 }
158
SetGrowthRate(TownID town_id,uint32 days_between_town_growth)159 /* static */ bool ScriptTown::SetGrowthRate(TownID town_id, uint32 days_between_town_growth)
160 {
161 EnforcePrecondition(false, IsValidTown(town_id));
162 uint16 growth_rate;
163 switch (days_between_town_growth) {
164 case TOWN_GROWTH_NORMAL:
165 growth_rate = 0;
166 break;
167
168 case TOWN_GROWTH_NONE:
169 growth_rate = TOWN_GROWTH_RATE_NONE;
170 break;
171
172 default:
173 EnforcePrecondition(false, (days_between_town_growth * DAY_TICKS / TOWN_GROWTH_TICKS) <= MAX_TOWN_GROWTH_TICKS);
174 /* Don't use growth_rate 0 as it means GROWTH_NORMAL */
175 growth_rate = std::max(days_between_town_growth * DAY_TICKS, 2u) - 1;
176 break;
177 }
178
179 return ScriptObject::DoCommand(::Town::Get(town_id)->xy, town_id, growth_rate, CMD_TOWN_GROWTH_RATE);
180 }
181
GetGrowthRate(TownID town_id)182 /* static */ int32 ScriptTown::GetGrowthRate(TownID town_id)
183 {
184 if (!IsValidTown(town_id)) return -1;
185
186 const Town *t = ::Town::Get(town_id);
187
188 if (t->growth_rate == TOWN_GROWTH_RATE_NONE) return TOWN_GROWTH_NONE;
189
190 return RoundDivSU(t->growth_rate + 1, DAY_TICKS);
191 }
192
GetDistanceManhattanToTile(TownID town_id,TileIndex tile)193 /* static */ int32 ScriptTown::GetDistanceManhattanToTile(TownID town_id, TileIndex tile)
194 {
195 return ScriptMap::DistanceManhattan(tile, GetLocation(town_id));
196 }
197
GetDistanceSquareToTile(TownID town_id,TileIndex tile)198 /* static */ int32 ScriptTown::GetDistanceSquareToTile(TownID town_id, TileIndex tile)
199 {
200 return ScriptMap::DistanceSquare(tile, GetLocation(town_id));
201 }
202
IsWithinTownInfluence(TownID town_id,TileIndex tile)203 /* static */ bool ScriptTown::IsWithinTownInfluence(TownID town_id, TileIndex tile)
204 {
205 if (!IsValidTown(town_id)) return false;
206
207 const Town *t = ::Town::Get(town_id);
208 return ((uint32)GetDistanceSquareToTile(town_id, tile) <= t->cache.squared_town_zone_radius[0]);
209 }
210
HasStatue(TownID town_id)211 /* static */ bool ScriptTown::HasStatue(TownID town_id)
212 {
213 if (ScriptObject::GetCompany() == OWNER_DEITY) return false;
214 if (!IsValidTown(town_id)) return false;
215
216 return ::HasBit(::Town::Get(town_id)->statues, ScriptObject::GetCompany());
217 }
218
IsCity(TownID town_id)219 /* static */ bool ScriptTown::IsCity(TownID town_id)
220 {
221 if (!IsValidTown(town_id)) return false;
222
223 return ::Town::Get(town_id)->larger_town;
224 }
225
GetRoadReworkDuration(TownID town_id)226 /* static */ int ScriptTown::GetRoadReworkDuration(TownID town_id)
227 {
228 if (!IsValidTown(town_id)) return -1;
229
230 return ::Town::Get(town_id)->road_build_months;
231 }
232
GetFundBuildingsDuration(TownID town_id)233 /* static */ int ScriptTown::GetFundBuildingsDuration(TownID town_id)
234 {
235 if (!IsValidTown(town_id)) return -1;
236
237 return ::Town::Get(town_id)->fund_buildings_months;
238 }
239
GetExclusiveRightsCompany(TownID town_id)240 /* static */ ScriptCompany::CompanyID ScriptTown::GetExclusiveRightsCompany(TownID town_id)
241 {
242 if (ScriptObject::GetCompany() == OWNER_DEITY) return ScriptCompany::COMPANY_INVALID;
243 if (!IsValidTown(town_id)) return ScriptCompany::COMPANY_INVALID;
244
245 return (ScriptCompany::CompanyID)(int8)::Town::Get(town_id)->exclusivity;
246 }
247
GetExclusiveRightsDuration(TownID town_id)248 /* static */ int32 ScriptTown::GetExclusiveRightsDuration(TownID town_id)
249 {
250 if (!IsValidTown(town_id)) return -1;
251
252 return ::Town::Get(town_id)->exclusive_counter;
253 }
254
IsActionAvailable(TownID town_id,TownAction town_action)255 /* static */ bool ScriptTown::IsActionAvailable(TownID town_id, TownAction town_action)
256 {
257 if (ScriptObject::GetCompany() == OWNER_DEITY) return false;
258 if (!IsValidTown(town_id)) return false;
259
260 return HasBit(::GetMaskOfTownActions(nullptr, ScriptObject::GetCompany(), ::Town::Get(town_id)), town_action);
261 }
262
PerformTownAction(TownID town_id,TownAction town_action)263 /* static */ bool ScriptTown::PerformTownAction(TownID town_id, TownAction town_action)
264 {
265 EnforcePrecondition(false, ScriptObject::GetCompany() != OWNER_DEITY);
266 EnforcePrecondition(false, IsValidTown(town_id));
267 EnforcePrecondition(false, IsActionAvailable(town_id, town_action));
268
269 return ScriptObject::DoCommand(::Town::Get(town_id)->xy, town_id, town_action, CMD_DO_TOWN_ACTION);
270 }
271
ExpandTown(TownID town_id,int houses)272 /* static */ bool ScriptTown::ExpandTown(TownID town_id, int houses)
273 {
274 EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY);
275 EnforcePrecondition(false, IsValidTown(town_id));
276 EnforcePrecondition(false, houses > 0);
277
278 return ScriptObject::DoCommand(::Town::Get(town_id)->xy, town_id, houses, CMD_EXPAND_TOWN);
279 }
280
FoundTown(TileIndex tile,TownSize size,bool city,RoadLayout layout,Text * name)281 /* static */ bool ScriptTown::FoundTown(TileIndex tile, TownSize size, bool city, RoadLayout layout, Text *name)
282 {
283 CCountedPtr<Text> counter(name);
284
285 EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY || _settings_game.economy.found_town != TF_FORBIDDEN);
286 EnforcePrecondition(false, ::IsValidTile(tile));
287 EnforcePrecondition(false, size == TOWN_SIZE_SMALL || size == TOWN_SIZE_MEDIUM || size == TOWN_SIZE_LARGE)
288 EnforcePrecondition(false, size != TOWN_SIZE_LARGE || ScriptObject::GetCompany() == OWNER_DEITY);
289 if (ScriptObject::GetCompany() == OWNER_DEITY || _settings_game.economy.found_town == TF_CUSTOM_LAYOUT) {
290 EnforcePrecondition(false, layout == ROAD_LAYOUT_ORIGINAL || layout == ROAD_LAYOUT_BETTER_ROADS || layout == ROAD_LAYOUT_2x2 || layout == ROAD_LAYOUT_3x3);
291 } else {
292 /* The layout parameter is ignored for AIs when custom layouts is disabled. */
293 layout = (RoadLayout) (byte)_settings_game.economy.town_layout;
294 }
295
296 const char *text = nullptr;
297 if (name != nullptr) {
298 text = name->GetDecodedText();
299 EnforcePreconditionEncodedText(false, text);
300 EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_TOWN_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG);
301 }
302 uint32 townnameparts;
303 if (!GenerateTownName(&townnameparts)) {
304 ScriptObject::SetLastError(ScriptError::ERR_NAME_IS_NOT_UNIQUE);
305 return false;
306 }
307
308 return ScriptObject::DoCommand(tile, size | (city ? 1 << 2 : 0) | layout << 3, townnameparts, CMD_FOUND_TOWN, text);
309 }
310
GetRating(TownID town_id,ScriptCompany::CompanyID company_id)311 /* static */ ScriptTown::TownRating ScriptTown::GetRating(TownID town_id, ScriptCompany::CompanyID company_id)
312 {
313 if (!IsValidTown(town_id)) return TOWN_RATING_INVALID;
314 ScriptCompany::CompanyID company = ScriptCompany::ResolveCompanyID(company_id);
315 if (company == ScriptCompany::COMPANY_INVALID) return TOWN_RATING_INVALID;
316
317 const Town *t = ::Town::Get(town_id);
318 if (!HasBit(t->have_ratings, company)) {
319 return TOWN_RATING_NONE;
320 } else if (t->ratings[company] <= RATING_APPALLING) {
321 return TOWN_RATING_APPALLING;
322 } else if (t->ratings[company] <= RATING_VERYPOOR) {
323 return TOWN_RATING_VERY_POOR;
324 } else if (t->ratings[company] <= RATING_POOR) {
325 return TOWN_RATING_POOR;
326 } else if (t->ratings[company] <= RATING_MEDIOCRE) {
327 return TOWN_RATING_MEDIOCRE;
328 } else if (t->ratings[company] <= RATING_GOOD) {
329 return TOWN_RATING_GOOD;
330 } else if (t->ratings[company] <= RATING_VERYGOOD) {
331 return TOWN_RATING_VERY_GOOD;
332 } else if (t->ratings[company] <= RATING_EXCELLENT) {
333 return TOWN_RATING_EXCELLENT;
334 } else {
335 return TOWN_RATING_OUTSTANDING;
336 }
337 }
338
GetDetailedRating(TownID town_id,ScriptCompany::CompanyID company_id)339 /* static */ int ScriptTown::GetDetailedRating(TownID town_id, ScriptCompany::CompanyID company_id)
340 {
341 if (!IsValidTown(town_id)) return TOWN_RATING_INVALID;
342 ScriptCompany::CompanyID company = ScriptCompany::ResolveCompanyID(company_id);
343 if (company == ScriptCompany::COMPANY_INVALID) return TOWN_RATING_INVALID;
344
345 const Town *t = ::Town::Get(town_id);
346 return t->ratings[company];
347 }
348
ChangeRating(TownID town_id,ScriptCompany::CompanyID company_id,int delta)349 /* static */ bool ScriptTown::ChangeRating(TownID town_id, ScriptCompany::CompanyID company_id, int delta)
350 {
351 EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY);
352 EnforcePrecondition(false, IsValidTown(town_id));
353 ScriptCompany::CompanyID company = ScriptCompany::ResolveCompanyID(company_id);
354 EnforcePrecondition(false, company != ScriptCompany::COMPANY_INVALID);
355
356 const Town *t = ::Town::Get(town_id);
357 int16 new_rating = Clamp(t->ratings[company] + delta, RATING_MINIMUM, RATING_MAXIMUM);
358 if (new_rating == t->ratings[company]) return false;
359
360 uint16 p2 = 0;
361 memcpy(&p2, &new_rating, sizeof(p2));
362
363 return ScriptObject::DoCommand(0, town_id | (company_id << 16), p2, CMD_TOWN_RATING);
364 }
365
GetAllowedNoise(TownID town_id)366 /* static */ int ScriptTown::GetAllowedNoise(TownID town_id)
367 {
368 if (!IsValidTown(town_id)) return -1;
369
370 const Town *t = ::Town::Get(town_id);
371 if (_settings_game.economy.station_noise_level) {
372 return t->MaxTownNoise() - t->noise_reached;
373 }
374
375 int num = 0;
376 for (const Station *st : Station::Iterate()) {
377 if (st->town == t && (st->facilities & FACIL_AIRPORT) && st->airport.type != AT_OILRIG) num++;
378 }
379 return std::max(0, 2 - num);
380 }
381
GetRoadLayout(TownID town_id)382 /* static */ ScriptTown::RoadLayout ScriptTown::GetRoadLayout(TownID town_id)
383 {
384 if (!IsValidTown(town_id)) return ROAD_LAYOUT_INVALID;
385
386 return (ScriptTown::RoadLayout)((TownLayout)::Town::Get(town_id)->layout);
387 }
388