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