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/>.
8 /** @file script_town.cpp Implementation of ScriptTown. */
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 "../../town_cmd.h"
21 #include "table/strings.h"
23 #include "../../safeguards.h"
25 /* static */ SQInteger
ScriptTown::GetTownCount()
27 return ::Town::GetNumItems();
30 /* static */ bool ScriptTown::IsValidTown(TownID town_id
)
32 return ::Town::IsValidID(town_id
);
35 /* static */ std::optional
<std::string
> ScriptTown::GetName(TownID town_id
)
37 if (!IsValidTown(town_id
)) return std::nullopt
;
39 ::SetDParam(0, town_id
);
40 return GetString(STR_TOWN_NAME
);
43 /* static */ bool ScriptTown::SetName(TownID town_id
, Text
*name
)
45 ScriptObjectRef
counter(name
);
47 EnforceDeityMode(false);
48 EnforcePrecondition(false, IsValidTown(town_id
));
50 if (name
!= nullptr) {
51 text
= name
->GetDecodedText();
52 EnforcePreconditionCustomError(false, ::Utf8StringLength(text
) < MAX_LENGTH_TOWN_NAME_CHARS
, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG
);
55 return ScriptObject::Command
<CMD_RENAME_TOWN
>::Do(town_id
, text
);
58 /* static */ bool ScriptTown::SetText(TownID town_id
, Text
*text
)
60 ScriptObjectRef
counter(text
);
62 EnforceDeityMode(false);
63 EnforcePrecondition(false, IsValidTown(town_id
));
65 return ScriptObject::Command
<CMD_TOWN_SET_TEXT
>::Do(town_id
, text
!= nullptr ? text
->GetEncodedText() : std::string
{});
68 /* static */ SQInteger
ScriptTown::GetPopulation(TownID town_id
)
70 if (!IsValidTown(town_id
)) return -1;
71 const Town
*t
= ::Town::Get(town_id
);
72 return t
->cache
.population
;
75 /* static */ SQInteger
ScriptTown::GetHouseCount(TownID town_id
)
77 if (!IsValidTown(town_id
)) return -1;
78 const Town
*t
= ::Town::Get(town_id
);
79 return t
->cache
.num_houses
;
82 /* static */ TileIndex
ScriptTown::GetLocation(TownID town_id
)
84 if (!IsValidTown(town_id
)) return INVALID_TILE
;
85 const Town
*t
= ::Town::Get(town_id
);
89 /* static */ SQInteger
ScriptTown::GetLastMonthProduction(TownID town_id
, CargoID cargo_id
)
91 if (!IsValidTown(town_id
)) return -1;
92 if (!ScriptCargo::IsValidCargo(cargo_id
)) return -1;
94 const Town
*t
= ::Town::Get(town_id
);
96 return t
->supplied
[cargo_id
].old_max
;
99 /* static */ SQInteger
ScriptTown::GetLastMonthSupplied(TownID town_id
, CargoID cargo_id
)
101 if (!IsValidTown(town_id
)) return -1;
102 if (!ScriptCargo::IsValidCargo(cargo_id
)) return -1;
104 const Town
*t
= ::Town::Get(town_id
);
106 return t
->supplied
[cargo_id
].old_act
;
109 /* static */ SQInteger
ScriptTown::GetLastMonthTransportedPercentage(TownID town_id
, CargoID cargo_id
)
111 if (!IsValidTown(town_id
)) return -1;
112 if (!ScriptCargo::IsValidCargo(cargo_id
)) return -1;
114 const Town
*t
= ::Town::Get(town_id
);
115 return ::ToPercent8(t
->GetPercentTransported(cargo_id
));
118 /* static */ SQInteger
ScriptTown::GetLastMonthReceived(TownID town_id
, ScriptCargo::TownEffect towneffect_id
)
120 if (!IsValidTown(town_id
)) return -1;
121 if (!ScriptCargo::IsValidTownEffect(towneffect_id
)) return -1;
123 const Town
*t
= ::Town::Get(town_id
);
125 return t
->received
[towneffect_id
].old_act
;
128 /* static */ bool ScriptTown::SetCargoGoal(TownID town_id
, ScriptCargo::TownEffect towneffect_id
, SQInteger goal
)
130 EnforceDeityMode(false);
131 EnforcePrecondition(false, IsValidTown(town_id
));
132 EnforcePrecondition(false, ScriptCargo::IsValidTownEffect(towneffect_id
));
134 goal
= Clamp
<SQInteger
>(goal
, 0, UINT32_MAX
);
136 return ScriptObject::Command
<CMD_TOWN_CARGO_GOAL
>::Do(town_id
, (::TownAcceptanceEffect
)towneffect_id
, goal
);
139 /* static */ SQInteger
ScriptTown::GetCargoGoal(TownID town_id
, ScriptCargo::TownEffect towneffect_id
)
141 if (!IsValidTown(town_id
)) return -1;
142 if (!ScriptCargo::IsValidTownEffect(towneffect_id
)) return -1;
144 const Town
*t
= ::Town::Get(town_id
);
146 switch (t
->goal
[towneffect_id
]) {
147 case TOWN_GROWTH_WINTER
:
148 if (TileHeight(t
->xy
) >= GetSnowLine() && t
->cache
.population
> 90) return 1;
151 case TOWN_GROWTH_DESERT
:
152 if (GetTropicZone(t
->xy
) == TROPICZONE_DESERT
&& t
->cache
.population
> 60) return 1;
155 default: return t
->goal
[towneffect_id
];
159 /* static */ bool ScriptTown::SetGrowthRate(TownID town_id
, SQInteger days_between_town_growth
)
161 EnforceDeityMode(false);
162 EnforcePrecondition(false, IsValidTown(town_id
));
163 uint16_t growth_rate
;
164 switch (days_between_town_growth
) {
165 case TOWN_GROWTH_NORMAL
:
169 case TOWN_GROWTH_NONE
:
170 growth_rate
= TOWN_GROWTH_RATE_NONE
;
174 EnforcePrecondition(false, (days_between_town_growth
* ::Ticks::DAY_TICKS
/ ::Ticks::TOWN_GROWTH_TICKS
) <= MAX_TOWN_GROWTH_TICKS
);
175 /* Don't use growth_rate 0 as it means GROWTH_NORMAL */
176 growth_rate
= std::max
<SQInteger
>(days_between_town_growth
* ::Ticks::DAY_TICKS
, 2u) - 1;
180 return ScriptObject::Command
<CMD_TOWN_GROWTH_RATE
>::Do(town_id
, growth_rate
);
183 /* static */ SQInteger
ScriptTown::GetGrowthRate(TownID town_id
)
185 if (!IsValidTown(town_id
)) return -1;
187 const Town
*t
= ::Town::Get(town_id
);
189 if (t
->growth_rate
== TOWN_GROWTH_RATE_NONE
) return TOWN_GROWTH_NONE
;
191 return RoundDivSU(t
->growth_rate
+ 1, ::Ticks::DAY_TICKS
);
194 /* static */ SQInteger
ScriptTown::GetDistanceManhattanToTile(TownID town_id
, TileIndex tile
)
196 return ScriptMap::DistanceManhattan(tile
, GetLocation(town_id
));
199 /* static */ SQInteger
ScriptTown::GetDistanceSquareToTile(TownID town_id
, TileIndex tile
)
201 return ScriptMap::DistanceSquare(tile
, GetLocation(town_id
));
204 /* static */ bool ScriptTown::IsWithinTownInfluence(TownID town_id
, TileIndex tile
)
206 if (!IsValidTown(town_id
)) return false;
208 const Town
*t
= ::Town::Get(town_id
);
209 return ((uint32_t)GetDistanceSquareToTile(town_id
, tile
) <= t
->cache
.squared_town_zone_radius
[HZB_TOWN_EDGE
]);
212 /* static */ bool ScriptTown::HasStatue(TownID town_id
)
214 EnforceCompanyModeValid(false);
215 if (!IsValidTown(town_id
)) return false;
217 return ::HasBit(::Town::Get(town_id
)->statues
, ScriptObject::GetCompany());
220 /* static */ bool ScriptTown::IsCity(TownID town_id
)
222 if (!IsValidTown(town_id
)) return false;
224 return ::Town::Get(town_id
)->larger_town
;
227 /* static */ SQInteger
ScriptTown::GetRoadReworkDuration(TownID town_id
)
229 if (!IsValidTown(town_id
)) return -1;
231 return ::Town::Get(town_id
)->road_build_months
;
234 /* static */ SQInteger
ScriptTown::GetFundBuildingsDuration(TownID town_id
)
236 if (!IsValidTown(town_id
)) return -1;
238 return ::Town::Get(town_id
)->fund_buildings_months
;
241 /* static */ ScriptCompany::CompanyID
ScriptTown::GetExclusiveRightsCompany(TownID town_id
)
243 EnforceCompanyModeValid(ScriptCompany::COMPANY_INVALID
);
244 if (!IsValidTown(town_id
)) return ScriptCompany::COMPANY_INVALID
;
246 return (ScriptCompany::CompanyID
)(int8_t)::Town::Get(town_id
)->exclusivity
;
249 /* static */ SQInteger
ScriptTown::GetExclusiveRightsDuration(TownID town_id
)
251 if (!IsValidTown(town_id
)) return -1;
253 return ::Town::Get(town_id
)->exclusive_counter
;
256 /* static */ bool ScriptTown::IsActionAvailable(TownID town_id
, TownAction town_action
)
258 EnforceCompanyModeValid(false);
259 if (!IsValidTown(town_id
)) return false;
261 return HasBit(::GetMaskOfTownActions(ScriptObject::GetCompany(), ::Town::Get(town_id
)), town_action
);
264 /* static */ bool ScriptTown::PerformTownAction(TownID town_id
, TownAction town_action
)
266 EnforceCompanyModeValid(false);
267 EnforcePrecondition(false, IsValidTown(town_id
));
268 EnforcePrecondition(false, IsActionAvailable(town_id
, town_action
));
270 return ScriptObject::Command
<CMD_DO_TOWN_ACTION
>::Do(town_id
, town_action
);
273 /* static */ bool ScriptTown::ExpandTown(TownID town_id
, SQInteger houses
)
275 EnforceDeityMode(false);
276 EnforcePrecondition(false, IsValidTown(town_id
));
277 EnforcePrecondition(false, houses
> 0);
279 houses
= std::min
<SQInteger
>(houses
, UINT32_MAX
);
281 return ScriptObject::Command
<CMD_EXPAND_TOWN
>::Do(town_id
, houses
);
284 /* static */ bool ScriptTown::FoundTown(TileIndex tile
, TownSize size
, bool city
, RoadLayout layout
, Text
*name
)
286 ScriptObjectRef
counter(name
);
288 EnforceDeityOrCompanyModeValid(false);
289 EnforcePrecondition(false, ScriptCompanyMode::IsDeity() || _settings_game
.economy
.found_town
!= TF_FORBIDDEN
);
290 EnforcePrecondition(false, ::IsValidTile(tile
));
291 EnforcePrecondition(false, size
== TOWN_SIZE_SMALL
|| size
== TOWN_SIZE_MEDIUM
|| size
== TOWN_SIZE_LARGE
)
292 EnforcePrecondition(false, ScriptCompanyMode::IsDeity() || size
!= TOWN_SIZE_LARGE
);
293 if (ScriptCompanyMode::IsDeity() || _settings_game
.economy
.found_town
== TF_CUSTOM_LAYOUT
) {
294 EnforcePrecondition(false, layout
>= ROAD_LAYOUT_ORIGINAL
&& layout
<= ROAD_LAYOUT_RANDOM
);
296 /* The layout parameter is ignored for AIs when custom layouts is disabled. */
297 layout
= (RoadLayout
) (uint8_t)_settings_game
.economy
.town_layout
;
301 if (name
!= nullptr) {
302 text
= name
->GetDecodedText();
303 EnforcePreconditionCustomError(false, ::Utf8StringLength(text
) < MAX_LENGTH_TOWN_NAME_CHARS
, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG
);
305 uint32_t townnameparts
;
306 if (!GenerateTownName(ScriptObject::GetRandomizer(), &townnameparts
)) {
307 ScriptObject::SetLastError(ScriptError::ERR_NAME_IS_NOT_UNIQUE
);
311 return ScriptObject::Command
<CMD_FOUND_TOWN
>::Do(tile
, (::TownSize
)size
, city
, (::TownLayout
)layout
, false, townnameparts
, text
);
314 /* static */ ScriptTown::TownRating
ScriptTown::GetRating(TownID town_id
, ScriptCompany::CompanyID company_id
)
316 if (!IsValidTown(town_id
)) return TOWN_RATING_INVALID
;
317 ScriptCompany::CompanyID company
= ScriptCompany::ResolveCompanyID(company_id
);
318 if (company
== ScriptCompany::COMPANY_INVALID
) return TOWN_RATING_INVALID
;
320 const Town
*t
= ::Town::Get(town_id
);
321 if (!HasBit(t
->have_ratings
, company
)) {
322 return TOWN_RATING_NONE
;
323 } else if (t
->ratings
[company
] <= RATING_APPALLING
) {
324 return TOWN_RATING_APPALLING
;
325 } else if (t
->ratings
[company
] <= RATING_VERYPOOR
) {
326 return TOWN_RATING_VERY_POOR
;
327 } else if (t
->ratings
[company
] <= RATING_POOR
) {
328 return TOWN_RATING_POOR
;
329 } else if (t
->ratings
[company
] <= RATING_MEDIOCRE
) {
330 return TOWN_RATING_MEDIOCRE
;
331 } else if (t
->ratings
[company
] <= RATING_GOOD
) {
332 return TOWN_RATING_GOOD
;
333 } else if (t
->ratings
[company
] <= RATING_VERYGOOD
) {
334 return TOWN_RATING_VERY_GOOD
;
335 } else if (t
->ratings
[company
] <= RATING_EXCELLENT
) {
336 return TOWN_RATING_EXCELLENT
;
338 return TOWN_RATING_OUTSTANDING
;
342 /* static */ SQInteger
ScriptTown::GetDetailedRating(TownID town_id
, ScriptCompany::CompanyID company_id
)
344 if (!IsValidTown(town_id
)) return TOWN_RATING_INVALID
;
345 ScriptCompany::CompanyID company
= ScriptCompany::ResolveCompanyID(company_id
);
346 if (company
== ScriptCompany::COMPANY_INVALID
) return TOWN_RATING_INVALID
;
348 const Town
*t
= ::Town::Get(town_id
);
349 return t
->ratings
[company
];
352 /* static */ bool ScriptTown::ChangeRating(TownID town_id
, ScriptCompany::CompanyID company_id
, SQInteger delta
)
354 EnforceDeityMode(false);
355 EnforcePrecondition(false, IsValidTown(town_id
));
356 ScriptCompany::CompanyID company
= ScriptCompany::ResolveCompanyID(company_id
);
357 EnforcePrecondition(false, company
!= ScriptCompany::COMPANY_INVALID
);
359 const Town
*t
= ::Town::Get(town_id
);
360 int16_t new_rating
= Clamp(t
->ratings
[company
] + delta
, RATING_MINIMUM
, RATING_MAXIMUM
);
361 if (new_rating
== t
->ratings
[company
]) return false;
363 return ScriptObject::Command
<CMD_TOWN_RATING
>::Do(town_id
, (::CompanyID
)company_id
, new_rating
);
366 /* static */ SQInteger
ScriptTown::GetAllowedNoise(TownID town_id
)
368 if (!IsValidTown(town_id
)) return -1;
370 const Town
*t
= ::Town::Get(town_id
);
371 if (_settings_game
.economy
.station_noise_level
) {
372 return t
->MaxTownNoise() - t
->noise_reached
;
376 for (const Station
*st
: Station::Iterate()) {
377 if (st
->town
== t
&& (st
->facilities
& FACIL_AIRPORT
) && st
->airport
.type
!= AT_OILRIG
) num
++;
379 return std::max(0, 2 - num
);
382 /* static */ ScriptTown::RoadLayout
ScriptTown::GetRoadLayout(TownID town_id
)
384 if (!IsValidTown(town_id
)) return ROAD_LAYOUT_INVALID
;
386 return (ScriptTown::RoadLayout
)((TownLayout
)::Town::Get(town_id
)->layout
);